protected static async Task <ProcessExecutionResult> RunAsyncInternal( Process process, ILog log, ILog stdout, ILog stderr, Action <int, int> kill, Func <ILog, int, IList <int> > getChildProcessIds, TimeSpan?timeout = null, Dictionary <string, string>?environmentVariables = null, CancellationToken?cancellationToken = null, bool?diagnostics = null) { var stdoutCompletion = new TaskCompletionSource <bool>(); var stderrCompletion = new TaskCompletionSource <bool>(); var result = new ProcessExecutionResult(); process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; // Make cute emojiis show up as cute emojiis in the output instead of ugly text symbols! process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; process.StartInfo.UseShellExecute = false; if (environmentVariables != null) { foreach (var kvp in environmentVariables) { if (kvp.Value == null) { process.StartInfo.EnvironmentVariables.Remove(kvp.Key); } else { process.StartInfo.EnvironmentVariables[kvp.Key] = kvp.Value; } } } process.OutputDataReceived += (sender, e) => { if (e.Data != null) { lock (stdout) { stdout.WriteLine(e.Data); stdout.Flush(); } } else { stdoutCompletion.TrySetResult(true); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data != null) { lock (stderr) { stderr.WriteLine(e.Data); stderr.Flush(); } } else { stderrCompletion.TrySetResult(true); } }; var sb = new StringBuilder(); sb.AppendLine($"Running {StringUtils.Quote(process.StartInfo.FileName)} {process.StartInfo.Arguments}"); if (process.StartInfo.EnvironmentVariables != null) { var currentEnvironment = Environment.GetEnvironmentVariables().Cast <DictionaryEntry>().ToDictionary(v => v.Key.ToString(), v => v.Value?.ToString(), StringComparer.Ordinal); var processEnvironment = process.StartInfo.EnvironmentVariables.Cast <DictionaryEntry>().ToDictionary(v => v.Key.ToString(), v => v.Value?.ToString(), StringComparer.Ordinal); var allVariables = currentEnvironment.Keys.Union(processEnvironment.Keys).Distinct(); bool headerShown = false; foreach (var variable in allVariables) { if (variable == null) { continue; } currentEnvironment.TryGetValue(variable, out var a); processEnvironment.TryGetValue(variable, out var b); if (a != b) { if (!headerShown) { sb.Append("With env vars: "); headerShown = true; } sb.Append($"{variable}={StringUtils.Quote(b)} "); } } } log.WriteLine(sb.ToString()); process.Start(); var pid = process.Id; process.BeginErrorReadLine(); process.BeginOutputReadLine(); cancellationToken?.Register(() => { var hasExited = false; try { hasExited = process.HasExited; } catch { // Process.HasExited can sometimes throw exceptions, so // just ignore those and to be safe treat it as the // process didn't exit (the safe option being to not leave // processes behind). } if (!hasExited) { stderr.WriteLine($"Killing process {pid} as it was cancelled"); kill(pid, 9); } }); if (timeout.HasValue) { if (!await WaitForExitAsync(process, timeout.Value)) { log.WriteLine($"Process {pid} didn't exit within {timeout} and will be killed"); await KillTreeAsync(pid, log, kill, getChildProcessIds, diagnostics ?? true); result.TimedOut = true; lock (stderr) { log.WriteLine($"{pid} Execution timed out after {timeout.Value.TotalSeconds} seconds and the process was killed."); } } } else { await WaitForExitAsync(process); } if (process.HasExited) { // make sure redirected output events are finished process.WaitForExit(); } Task.WaitAll(new Task[] { stderrCompletion.Task, stdoutCompletion.Task }, TimeSpan.FromSeconds(1)); try { result.ExitCode = process.ExitCode; log.WriteLine($"Process {Path.GetFileName(process.StartInfo.FileName)} exited with {result.ExitCode}"); } catch (Exception e) { result.ExitCode = 12345678; log.WriteLine($"Failed to get ExitCode: {e}"); } return(result); }
protected static async Task <ProcessExecutionResult> RunAsyncInternal( Process process, ILog log, ILog stdout, ILog stderr, Action <int, int> kill, Func <ILog, int, IList <int> > getChildrenPS, TimeSpan?timeout = null, Dictionary <string, string>?environmentVariables = null, CancellationToken?cancellationToken = null, bool?diagnostics = null) { var stdout_completion = new TaskCompletionSource <bool>(); var stderr_completion = new TaskCompletionSource <bool>(); var rv = new ProcessExecutionResult(); process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; // Make cute emojiis show up as cute emojiis in the output instead of ugly text symbols! process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; process.StartInfo.UseShellExecute = false; if (environmentVariables != null) { foreach (var kvp in environmentVariables) { if (kvp.Value == null) { process.StartInfo.EnvironmentVariables.Remove(kvp.Key); } else { process.StartInfo.EnvironmentVariables[kvp.Key] = kvp.Value; } } } process.OutputDataReceived += (sender, e) => { if (e.Data != null) { lock (stdout) { stdout.WriteLine(e.Data); stdout.Flush(); } } else { stdout_completion.TrySetResult(true); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data != null) { lock (stderr) { stderr.WriteLine(e.Data); stderr.Flush(); } } else { stderr_completion.TrySetResult(true); } }; var sb = new StringBuilder(); if (process.StartInfo.EnvironmentVariables != null) { var currentEnvironment = Environment.GetEnvironmentVariables().Cast <System.Collections.DictionaryEntry>().ToDictionary((v) => (string)v.Key, (v) => (string?)v.Value, StringComparer.Ordinal); var processEnvironment = process.StartInfo.EnvironmentVariables.Cast <System.Collections.DictionaryEntry>().ToDictionary((v) => (string)v.Key, (v) => (string?)v.Value, StringComparer.Ordinal); var allKeys = currentEnvironment.Keys.Union(processEnvironment.Keys).Distinct(); foreach (var key in allKeys) { if (key == null) { continue; } string?a = null, b = null; currentEnvironment?.TryGetValue(key !, out a); processEnvironment?.TryGetValue(key !, out b); if (a != b) { sb.Append($"{key}={StringUtils.Quote(b)} "); } } } sb.Append($"{StringUtils.Quote(process.StartInfo.FileName)} {process.StartInfo.Arguments}"); log.WriteLine(sb); process.Start(); var pid = process.Id; process.BeginErrorReadLine(); process.BeginOutputReadLine(); cancellationToken?.Register(() => { var hasExited = false; try { hasExited = process.HasExited; } catch { // Process.HasExited can sometimes throw exceptions, so // just ignore those and to be safe treat it as the // process didn't exit (the safe option being to not leave // processes behind). } if (!hasExited) { stderr.WriteLine($"Execution of {pid} was cancelled."); kill(pid, 9); } }); if (timeout.HasValue) { if (!await WaitForExitAsync(process, timeout.Value)) { await KillTreeAsyncInternal(process.Id, kill, getChildrenPS, log, diagnostics ?? true); rv.TimedOut = true; lock (stderr) log.WriteLine($"{pid} Execution timed out after {timeout.Value.TotalSeconds} seconds and the process was killed."); } } await WaitForExitAsync(process); Task.WaitAll(new Task[] { stderr_completion.Task, stdout_completion.Task }, TimeSpan.FromSeconds(1)); try { rv.ExitCode = process.ExitCode; } catch (Exception e) { rv.ExitCode = 12345678; log.WriteLine($"Failed to get ExitCode: {e}"); } return(rv); }