public CookiecutterClient(CookiecutterPythonInterpreter interpreter, Redirector redirector) { _interpreter = interpreter; var localAppDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); _envFolderPath = Path.Combine(localAppDataFolderPath, "Microsoft", "CookiecutterTools", "env"); _envInterpreterPath = Path.Combine(_envFolderPath, "scripts", "python.exe"); _redirector = redirector; }
public static ICookiecutterClient Create(IServiceProvider provider, Redirector redirector) { var interpreter = FindCompatibleInterpreter(); if (interpreter != null) { return new CookiecutterClient(provider, interpreter, redirector); } return null; }
protected override void OnCreate() { _outputWindow = OutputWindowRedirector.GetGeneral(this); Debug.Assert(_outputWindow != null); _statusBar = GetService(typeof(SVsStatusbar)) as IVsStatusbar; _uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; _dte = GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; _infoBarFactory = GetService(typeof(SVsInfoBarUIFactory)) as IVsInfoBarUIFactory; object control = null; if (!CookiecutterClientProvider.IsCompatiblePythonAvailable()) { ReportPrereqsEvent(false); control = new MissingDependencies(); } else { ReportPrereqsEvent(true); string feedUrl = CookiecutterPackage.Instance.RecommendedFeed; if (string.IsNullOrEmpty(feedUrl)) { feedUrl = UrlConstants.DefaultRecommendedFeed; } _cookiecutterControl = new CookiecutterControl(_outputWindow, CookiecutterTelemetry.Current, new Uri(feedUrl), OpenGeneratedFolder, UpdateCommandUI); _cookiecutterControl.ContextMenuRequested += OnContextMenuRequested; control = _cookiecutterControl; _cookiecutterControl.InitializeAsync(CookiecutterPackage.Instance.CheckForTemplateUpdate).HandleAllExceptions(this, GetType()).DoNotWait(); } Content = control; RegisterCommands(new Command[] { new HomeCommand(this), new RunCommand(this), new UpdateCommand(this), new CheckForUpdatesCommand(this), new GitHubCommand(this, PackageIds.cmdidLinkGitHubHome), new GitHubCommand(this, PackageIds.cmdidLinkGitHubIssues), new GitHubCommand(this, PackageIds.cmdidLinkGitHubWiki), }, PackageGuids.guidCookiecutterCmdSet); RegisterCommands(new Command[] { new DeleteInstalledTemplateCommand(this), }, VSConstants.GUID_VSStandardCommandSet97); base.OnCreate(); OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (CookiecutterPackage.Instance.ShowHelp) { AddInfoBar(); } }
public CookiecutterControl(Redirector outputWindow, ICookiecutterTelemetry telemetry, Uri feedUrl, Action<string> openFolder, Action updateCommandUI) { _updateCommandUI = updateCommandUI; _checkForUpdatesTimer = new DispatcherTimer(); _checkForUpdatesTimer.Tick += new EventHandler(CheckForUpdateTimer_Tick); string gitExeFilePath = GitClient.RecommendedGitFilePath; var gitClient = new GitClient(gitExeFilePath, outputWindow); var gitHubClient = new GitHubClient(); ViewModel = new CookiecutterViewModel( CookiecutterClientProvider.Create(outputWindow), gitHubClient, gitClient, telemetry, outputWindow, new LocalTemplateSource(CookiecutterViewModel.DefaultInstalledFolderPath, gitClient), new FeedTemplateSource(feedUrl), new GitHubTemplateSource(gitHubClient), openFolder ); ViewModel.UserConfigFilePath = CookiecutterViewModel.GetUserConfigPath(); ViewModel.OutputFolderPath = string.Empty; // leaving this empty for now, force user to enter one ViewModel.ContextLoaded += ViewModel_ContextLoaded; ViewModel.HomeClicked += ViewModel_HomeClicked; _searchPage = new CookiecutterSearchPage { DataContext = ViewModel }; _optionsPage = new CookiecutterOptionsPage { DataContext = ViewModel }; var pages = new List<Page>(); pages.Add(_searchPage); pages.Add(_optionsPage); _pageSequence = new CollectionViewSource { Source = new ObservableCollection<Page>(pages) }; PageCount = _pageSequence.View.OfType<object>().Count(); PageSequence = _pageSequence.View; PageSequence.MoveCurrentToFirst(); DataContext = this; InitializeComponent(); _searchPage.SelectedTemplateChanged += SearchPage_SelectedTemplateChanged; }
public static IGitClient Create(Redirector redirector, string commonIdeFolderPath) { string gitExeFilePath = null; // Try to locate Team Explorer's git.exe using the running instance ide folder if (!string.IsNullOrEmpty(commonIdeFolderPath)) { gitExeFilePath = GetTeamExplorerGitFilePathFromIdeFolderPath(commonIdeFolderPath); } // Try to locate Team Explorer's git.exe using the Dev 15 install path from registry // (for tests with no running instance of VS, or when running in Dev 14) if (!File.Exists(gitExeFilePath)) { gitExeFilePath = GetTeamExplorerGitFilePathFromRegistry(); } // Just use git.exe, and it will work if it's in PATH // If it's not, the error will be output in redirector at time of use if (!File.Exists(gitExeFilePath)) { gitExeFilePath = GitExecutableName; } return new GitClient(gitExeFilePath, redirector); }
public GitClient(string gitExeFilePath, Redirector redirector) { _gitExeFilePath = gitExeFilePath; _redirector = redirector; }
public Task<string> CloneAsync(Redirector redirector, string repoUrl, string targetParentFolderPath) { throw new NotImplementedException(); }
public CookiecutterViewModel(ICookiecutterClient cutter, IGitHubClient githubClient, IGitClient gitClient, ICookiecutterTelemetry telemetry, Redirector outputWindow, ILocalTemplateSource installedTemplateSource, ITemplateSource feedTemplateSource, ITemplateSource gitHubTemplateSource, Action<string> openFolder, IProjectSystemClient projectSystemClient) { _cutterClient = cutter; _githubClient = githubClient; _gitClient = gitClient; _telemetry = telemetry; _outputWindow = outputWindow; _recommendedSource = feedTemplateSource; _installedSource = installedTemplateSource; _githubSource = gitHubTemplateSource; _openFolder = openFolder; _projectSystemClient = projectSystemClient; Installed = new CategorizedViewModel(Strings.TemplateCategoryInstalled); Recommended = new CategorizedViewModel(Strings.TemplateCategoryRecommended); GitHub = new CategorizedViewModel(Strings.TemplateCategoryGitHub); Custom = new CategorizedViewModel(Strings.TemplateCategoryCustom); }
private static async Task<ProcessOutputResult> RunGenerateContextScript(Redirector redirector, string interpreterPath, string templateFolderPath, string userConfigFilePath) { var scriptPath = PythonToolsInstallPath.GetFile("cookiecutter_load.py"); return await RunPythonScript(redirector, interpreterPath, scriptPath, string.Format("\"{0}\" \"{1}\"", templateFolderPath, userConfigFilePath)); }
/// <summary> /// Runs the file with the provided settings as a user with /// administrative permissions. The window is always hidden and output /// is provided to the redirector when the process terminates. /// </summary> /// <param name="filename">Executable file to run.</param> /// <param name="arguments">Arguments to pass.</param> /// <param name="workingDirectory">Starting directory.</param> /// <param name="redirector"> /// An object to receive redirected output. /// </param> /// <param name="quoteArgs"></param> /// <returns>A <see cref="ProcessOutput"/> object.</returns> public static ProcessOutput RunElevated( string filename, IEnumerable <string> arguments, string workingDirectory, IEnumerable <KeyValuePair <string, string> > env, Redirector redirector, bool quoteArgs = true, bool elevate = true, Encoding outputEncoding = null, Encoding errorEncoding = null ) { var psi = new ProcessStartInfo(PythonToolsInstallPath.GetFile("Microsoft.PythonTools.RunElevated.exe", typeof(ProcessOutput).Assembly)); psi.CreateNoWindow = true; psi.WindowStyle = ProcessWindowStyle.Hidden; var utf8 = new UTF8Encoding(false); // Send args and env as base64 to avoid newline issues string args; if (quoteArgs) { args = string.Join("|", arguments .Where(a => a != null) .Select(a => Convert.ToBase64String(utf8.GetBytes(QuoteSingleArgument(a)))) ); } else { args = string.Join("|", arguments .Where(a => a != null) .Select(a => Convert.ToBase64String(utf8.GetBytes(a))) ); } var fullEnv = env != null? string.Join("|", env.Select(kv => kv.Key + "=" + Convert.ToBase64String(utf8.GetBytes(kv.Value)))) : ""; TcpListener listener = null; Task <TcpClient> clientTask = null; try { listener = SocketUtils.GetRandomPortListener(IPAddress.Loopback, out int port); psi.Arguments = port.ToString(); clientTask = listener.AcceptTcpClientAsync(); } catch (Exception ex) { listener?.Stop(); throw new InvalidOperationException(Strings.UnableToElevate, ex); } var process = new Process(); clientTask.ContinueWith(t => { listener.Stop(); TcpClient client; try { client = t.Result; } catch (AggregateException ae) { try { process.Kill(); } catch (InvalidOperationException) { } catch (Win32Exception) { } if (redirector != null) { foreach (var ex in ae.InnerExceptions.DefaultIfEmpty(ae)) { using (var reader = new StringReader(ex.ToString())) { for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) { redirector.WriteErrorLine(line); } } } } return; } using (var writer = new StreamWriter(client.GetStream(), utf8, 4096, true)) { writer.WriteLine(filename); writer.WriteLine(args); writer.WriteLine(workingDirectory); writer.WriteLine(fullEnv); writer.WriteLine(outputEncoding?.WebName ?? ""); writer.WriteLine(errorEncoding?.WebName ?? ""); } if (redirector != null) { var reader = new StreamReader(client.GetStream(), utf8, false, 4096, true); Task.Run(() => { try { for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (line.StartsWithOrdinal("OUT:")) { redirector.WriteLine(line.Substring(4)); } else if (line.StartsWithOrdinal("ERR:")) { redirector.WriteErrorLine(line.Substring(4)); } else { redirector.WriteLine(line); } } } catch (IOException) { } catch (ObjectDisposedException) { } }); } }); process.StartInfo = psi; return(new ProcessOutput(process, redirector)); }
private ProcessOutput(Process process, Redirector redirector) { _arguments = QuoteSingleArgument(process.StartInfo.FileName) + " " + process.StartInfo.Arguments; _redirector = redirector; if (_redirector == null) { _output = new List <string>(); _error = new List <string>(); _redirector = new ListRedirector(_output, _error); } _process = process; if (_process.StartInfo.RedirectStandardOutput) { _process.OutputDataReceived += OnOutputDataReceived; } if (_process.StartInfo.RedirectStandardError) { _process.ErrorDataReceived += OnErrorDataReceived; } if (!_process.StartInfo.RedirectStandardOutput && !_process.StartInfo.RedirectStandardError) { // If we are receiving output events, we signal that the process // has exited when one of them receives null. Otherwise, we have // to listen for the Exited event. // If we just listen for the Exited event, we may receive it // before all the output has arrived. _process.Exited += OnExited; } _process.EnableRaisingEvents = true; try { _process.Start(); } catch (Win32Exception ex) { _redirector.WriteErrorLine(ex.Message); _process = null; } catch (Exception ex) when(!ex.IsCriticalException()) { foreach (var line in SplitLines(ex.ToString())) { _redirector.WriteErrorLine(line); } _process = null; } if (_process != null) { if (_process.StartInfo.RedirectStandardOutput) { _process.BeginOutputReadLine(); } if (_process.StartInfo.RedirectStandardError) { _process.BeginErrorReadLine(); } if (_process.StartInfo.RedirectStandardInput) { // Close standard input so that we don't get stuck trying to read input from the user. try { _process.StandardInput.Close(); } catch (InvalidOperationException) { // StandardInput not available } } } }
private static async Task<ProcessOutputResult> RunRunScript(Redirector redirector, string interpreterPath, string templateFolderPath, string userConfigFilePath, string outputFolderPath, string contextPath) { var scriptPath = PythonToolsInstallPath.GetFile("cookiecutter_run.py"); return await RunPythonScript(redirector, interpreterPath, scriptPath, GetRunArguments(templateFolderPath, userConfigFilePath, outputFolderPath, contextPath)); }
/// <summary> /// Runs the file with the provided settings. /// </summary> /// <param name="filename">Executable file to run.</param> /// <param name="arguments">Arguments to pass.</param> /// <param name="workingDirectory">Starting directory.</param> /// <param name="env">Environment variables to set.</param> /// <param name="visible"> /// False to hide the window and redirect output to /// <see cref="StandardOutputLines"/> and /// <see cref="StandardErrorLines"/>. /// </param> /// <param name="redirector"> /// An object to receive redirected output. /// </param> /// <param name="quoteArgs"> /// True to ensure each argument is correctly quoted. /// </param> /// <param name="elevate"> /// True to run the process as an administrator. See /// <see cref="RunElevated"/>. /// </param> /// <returns>A <see cref="ProcessOutput"/> object.</returns> public static ProcessOutput Run( string filename, IEnumerable <string> arguments, string workingDirectory, IEnumerable <KeyValuePair <string, string> > env, bool visible, Redirector redirector, bool quoteArgs = true, bool elevate = false, Encoding outputEncoding = null, Encoding errorEncoding = null ) { if (string.IsNullOrEmpty(filename)) { throw new ArgumentException("Filename required", "filename"); } if (elevate) { return(RunElevated( filename, arguments, workingDirectory, env, redirector, quoteArgs, elevate, outputEncoding, errorEncoding )); } var psi = new ProcessStartInfo(filename); if (quoteArgs) { psi.Arguments = string.Join(" ", arguments.Where(a => a != null).Select(QuoteSingleArgument)); } else { psi.Arguments = string.Join(" ", arguments.Where(a => a != null)); } psi.WorkingDirectory = workingDirectory; psi.CreateNoWindow = !visible; psi.UseShellExecute = false; psi.RedirectStandardError = !visible || (redirector != null); psi.RedirectStandardOutput = !visible || (redirector != null); psi.RedirectStandardInput = !visible; psi.StandardOutputEncoding = outputEncoding ?? psi.StandardOutputEncoding; psi.StandardErrorEncoding = errorEncoding ?? outputEncoding ?? psi.StandardErrorEncoding; if (env != null) { foreach (var kv in env) { psi.EnvironmentVariables[kv.Key] = kv.Value; } } var process = new Process { StartInfo = psi }; return(new ProcessOutput(process, redirector)); }
private ProcessOutput(Process process, Redirector redirector) { _arguments = QuoteSingleArgument(process.StartInfo.FileName) + " " + process.StartInfo.Arguments; _redirector = redirector; if (_redirector == null) { _output = new List<string>(); _error = new List<string>(); _redirector = new ListRedirector(_output, _error); } _process = process; if (_process.StartInfo.RedirectStandardOutput) { _process.OutputDataReceived += OnOutputDataReceived; } if (_process.StartInfo.RedirectStandardError) { _process.ErrorDataReceived += OnErrorDataReceived; } if (!_process.StartInfo.RedirectStandardOutput && !_process.StartInfo.RedirectStandardError) { // If we are receiving output events, we signal that the process // has exited when one of them receives null. Otherwise, we have // to listen for the Exited event. // If we just listen for the Exited event, we may receive it // before all the output has arrived. _process.Exited += OnExited; } _process.EnableRaisingEvents = true; try { _process.Start(); } catch (Exception ex) when (!ex.IsCriticalException()) { foreach (var line in SplitLines(ex.ToString())) { _redirector.WriteErrorLine(line); } _process = null; } if (_process != null) { if (_process.StartInfo.RedirectStandardOutput) { _process.BeginOutputReadLine(); } if (_process.StartInfo.RedirectStandardError) { _process.BeginErrorReadLine(); } if (_process.StartInfo.RedirectStandardInput) { // Close standard input so that we don't get stuck trying to read input from the user. try { _process.StandardInput.Close(); } catch (InvalidOperationException) { // StandardInput not available } } } }
/// <summary> /// Runs the file with the provided settings as a user with /// administrative permissions. The window is always hidden and output /// is provided to the redirector when the process terminates. /// </summary> /// <param name="filename">Executable file to run.</param> /// <param name="arguments">Arguments to pass.</param> /// <param name="workingDirectory">Starting directory.</param> /// <param name="redirector"> /// An object to receive redirected output. /// </param> /// <param name="quoteArgs"></param> /// <returns>A <see cref="ProcessOutput"/> object.</returns> public static ProcessOutput RunElevated( string filename, IEnumerable<string> arguments, string workingDirectory, IEnumerable<KeyValuePair<string, string>> env, Redirector redirector, bool quoteArgs = true, bool elevate = true, Encoding outputEncoding = null, Encoding errorEncoding = null ) { var psi = new ProcessStartInfo(PythonToolsInstallPath.GetFile("Microsoft.PythonTools.RunElevated.exe", typeof(ProcessOutput).Assembly)); psi.CreateNoWindow = true; psi.WindowStyle = ProcessWindowStyle.Hidden; psi.UseShellExecute = true; psi.Verb = elevate ? "runas" : null; int port = GetFreePort(); var listener = new TcpListener(IPAddress.Loopback, port); psi.Arguments = port.ToString(); var utf8 = new UTF8Encoding(false); // Send args and env as base64 to avoid newline issues string args; if (quoteArgs) { args = string.Join("|", arguments .Where(a => a != null) .Select(a => Convert.ToBase64String(utf8.GetBytes(QuoteSingleArgument(a)))) ); } else { args = string.Join("|", arguments .Where(a => a != null) .Select(a => Convert.ToBase64String(utf8.GetBytes(a))) ); } var fullEnv = env != null ? string.Join("|", env.Select(kv => kv.Key + "=" + Convert.ToBase64String(utf8.GetBytes(kv.Value)))) : ""; listener.Start(); listener.AcceptTcpClientAsync().ContinueWith(t => { listener.Stop(); var client = t.Result; using (var writer = new StreamWriter(client.GetStream(), utf8, 4096, true)) { writer.WriteLine(filename); writer.WriteLine(args); writer.WriteLine(workingDirectory); writer.WriteLine(fullEnv); writer.WriteLine(outputEncoding?.WebName ?? ""); writer.WriteLine(errorEncoding?.WebName ?? ""); } if (redirector != null) { var reader = new StreamReader(client.GetStream(), utf8, false, 4096, true); Task.Run(() => { try { for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (line.StartsWith("OUT:")) { redirector.WriteLine(line.Substring(4)); } else if (line.StartsWith("ERR:")) { redirector.WriteErrorLine(line.Substring(4)); } else { redirector.WriteLine(line); } } } catch (IOException) { } catch (ObjectDisposedException) { } }); } }); var process = new Process(); process.StartInfo = psi; return new ProcessOutput(process, redirector); }
/// <summary> /// Runs the file with the provided settings. /// </summary> /// <param name="filename">Executable file to run.</param> /// <param name="arguments">Arguments to pass.</param> /// <param name="workingDirectory">Starting directory.</param> /// <param name="env">Environment variables to set.</param> /// <param name="visible"> /// False to hide the window and redirect output to /// <see cref="StandardOutputLines"/> and /// <see cref="StandardErrorLines"/>. /// </param> /// <param name="redirector"> /// An object to receive redirected output. /// </param> /// <param name="quoteArgs"> /// True to ensure each argument is correctly quoted. /// </param> /// <param name="elevate"> /// True to run the process as an administrator. See /// <see cref="RunElevated"/>. /// </param> /// <returns>A <see cref="ProcessOutput"/> object.</returns> public static ProcessOutput Run( string filename, IEnumerable<string> arguments, string workingDirectory, IEnumerable<KeyValuePair<string, string>> env, bool visible, Redirector redirector, bool quoteArgs = true, bool elevate = false, Encoding outputEncoding = null, Encoding errorEncoding = null ) { if (string.IsNullOrEmpty(filename)) { throw new ArgumentException("Filename required", "filename"); } if (elevate) { return RunElevated( filename, arguments, workingDirectory, env, redirector, quoteArgs, elevate, outputEncoding, errorEncoding ); } var psi = new ProcessStartInfo(filename); if (quoteArgs) { psi.Arguments = string.Join(" ", arguments.Where(a => a != null).Select(QuoteSingleArgument)); } else { psi.Arguments = string.Join(" ", arguments.Where(a => a != null)); } psi.WorkingDirectory = workingDirectory; psi.CreateNoWindow = !visible; psi.UseShellExecute = false; psi.RedirectStandardError = !visible || (redirector != null); psi.RedirectStandardOutput = !visible || (redirector != null); psi.RedirectStandardInput = !visible; psi.StandardOutputEncoding = outputEncoding ?? psi.StandardOutputEncoding; psi.StandardErrorEncoding = errorEncoding ?? outputEncoding ?? psi.StandardErrorEncoding; if (env != null) { foreach (var kv in env) { psi.EnvironmentVariables[kv.Key] = kv.Value; } } var process = new Process { StartInfo = psi }; return new ProcessOutput(process, redirector); }
/// <summary> /// Runs the file with the provided settings as a user with /// administrative permissions. The window is always hidden and output /// is provided to the redirector when the process terminates. /// </summary> /// <param name="filename">Executable file to run.</param> /// <param name="arguments">Arguments to pass.</param> /// <param name="workingDirectory">Starting directory.</param> /// <param name="redirector"> /// An object to receive redirected output. /// </param> /// <param name="quoteArgs"></param> /// <returns>A <see cref="ProcessOutput"/> object.</returns> public static ProcessOutput RunElevated( string filename, IEnumerable <string> arguments, string workingDirectory, IEnumerable <KeyValuePair <string, string> > env, Redirector redirector, bool quoteArgs = true, bool elevate = true, Encoding outputEncoding = null, Encoding errorEncoding = null ) { var psi = new ProcessStartInfo(PythonToolsInstallPath.GetFile("Microsoft.PythonTools.RunElevated.exe", typeof(ProcessOutput).Assembly)); psi.CreateNoWindow = true; psi.WindowStyle = ProcessWindowStyle.Hidden; psi.UseShellExecute = true; psi.Verb = elevate ? "runas" : null; int port = GetFreePort(); var listener = new TcpListener(IPAddress.Loopback, port); psi.Arguments = port.ToString(); var utf8 = new UTF8Encoding(false); // Send args and env as base64 to avoid newline issues string args; if (quoteArgs) { args = string.Join("|", arguments .Where(a => a != null) .Select(a => Convert.ToBase64String(utf8.GetBytes(QuoteSingleArgument(a)))) ); } else { args = string.Join("|", arguments .Where(a => a != null) .Select(a => Convert.ToBase64String(utf8.GetBytes(a))) ); } var fullEnv = env != null? string.Join("|", env.Select(kv => kv.Key + "=" + Convert.ToBase64String(utf8.GetBytes(kv.Value)))) : ""; listener.Start(); listener.AcceptTcpClientAsync().ContinueWith(t => { listener.Stop(); var client = t.Result; using (var writer = new StreamWriter(client.GetStream(), utf8, 4096, true)) { writer.WriteLine(filename); writer.WriteLine(args); writer.WriteLine(workingDirectory); writer.WriteLine(fullEnv); writer.WriteLine(outputEncoding?.WebName ?? ""); writer.WriteLine(errorEncoding?.WebName ?? ""); } if (redirector != null) { var reader = new StreamReader(client.GetStream(), utf8, false, 4096, true); Task.Run(() => { try { for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (line.StartsWith("OUT:")) { redirector.WriteLine(line.Substring(4)); } else if (line.StartsWith("ERR:")) { redirector.WriteErrorLine(line.Substring(4)); } else { redirector.WriteLine(line); } } } catch (IOException) { } catch (ObjectDisposedException) { } }); } }); var process = new Process(); process.StartInfo = psi; return(new ProcessOutput(process, redirector)); }
private static async Task<ProcessOutputResult> RunPythonScript(Redirector redirector, string interpreterPath, string script, string parameters) { var outputLines = new List<string>(); var errorLines = new List<string>(); ProcessOutput output = null; var arguments = string.Format("\"{0}\" {1}", script, parameters); var listRedirector = new ListRedirector(outputLines, errorLines); var outerRedirector = new TeeRedirector(redirector, listRedirector); output = ProcessOutput.Run(interpreterPath, new string[] { arguments }, null, null, false, outerRedirector); var result = await WaitForOutput(interpreterPath, output); result.StandardOutputLines = outputLines.ToArray(); result.StandardErrorLines = errorLines.ToArray(); return result; }