using System.Diagnostics; using System.Text; namespace Amazon.Common.DotNetCli.Tools { /// /// This class represents an instance of a process being executed, set up to capture /// STDOUT and STDERR and expose as properties. Once it's created, you can execute /// Run multiple times. /// public class ProcessInstance { /// /// Results of process execution /// public struct ProcessResults { /// /// True if process executed within timout /// public bool Executed { get; set; } /// /// Non-zero upon success /// public int? ExitCode { get; set; } /// /// Output captured from STDOUT /// public string Output { get; set; } /// /// Output captured from STDERR or other error information /// public string Error { get; set; } } private readonly ProcessStartInfo _info; /// /// Set up ProcessStartInfo, forcing values required to capture STDOUT and STDERR /// /// public ProcessInstance(ProcessStartInfo info) { _info = info; _info.RedirectStandardOutput = true; _info.RedirectStandardError = true; _info.UseShellExecute = false; } /// /// Run the process /// /// /// Process instance execution results public ProcessResults Run(int timeoutInMilliseconds = 60000) { var stdout = new StringBuilder(); var stderr = new StringBuilder(); using (var proc = new Process()) { proc.StartInfo = _info; proc.EnableRaisingEvents = true; proc.OutputDataReceived += (_, e) => { if (!string.IsNullOrEmpty(e.Data)) stdout.Append(e.Data); }; proc.ErrorDataReceived += (_, e) => { if (!string.IsNullOrEmpty(e.Data)) stderr.Append(e.Data); }; bool executed; int? exitCode; string output; string error; if (proc.Start()) { proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); executed = proc.WaitForExit(timeoutInMilliseconds); if (executed) proc.WaitForExit(); // this ensures STDOUT is completely captured else stderr.Append($"{(stderr.Length > 0 ? "\n" : "")}Timeout waiting for process"); exitCode = proc.ExitCode; output = stdout.ToString(); error = stderr.ToString(); } else { executed = false; exitCode = null; output = ""; error = "Unable to launch process"; } return new ProcessResults() { Executed = executed, ExitCode = exitCode, Output = output, Error = error }; } } } /// /// This class is a factory that generates executed Processes /// public class ProcessFactory: IProcessFactory { public static readonly IProcessFactory Default = new ProcessFactory(); /// /// Launch the process described by "info" and return the execution results. /// /// /// /// public ProcessInstance.ProcessResults RunProcess(ProcessStartInfo info, int timeout = 60000) { return new ProcessInstance(info).Run(timeout); } } public interface IProcessFactory { ProcessInstance.ProcessResults RunProcess(ProcessStartInfo info, int timeout = 60000); } }