public static ProcessResult Execute(this IProcessHandler handler, string executable, string arguments, int timeout = 30000) { var setup = new ProcessSetup() { Executable = executable, Arguments = arguments }; return(handler.Execute(setup, timeout)); }
public static async Task <ProcessResult> ExecuteAsync(this IProcessHandler handler, string executable, string arguments, CancellationToken cancel = default) { var setup = new ProcessSetup() { Executable = executable, Arguments = arguments }; return(await handler.ExecuteAsync(setup, cancel)); }
/// <summary> /// Set process parameters based on setup object. /// </summary> /// <param name="process"></param> /// <param name="setup"></param> private static void SetupProcess(Process process, ProcessSetup setup) { process.StartInfo.FileName = setup.Executable; if (!string.IsNullOrWhiteSpace(setup.WorkingDir)) { process.StartInfo.WorkingDirectory = setup.WorkingDir; } process.StartInfo.Arguments = setup.Arguments; process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; if (setup.EnvironmentVariables != null) { foreach (var kv in setup.EnvironmentVariables) { process.StartInfo.Environment.Add(kv.key, kv.value); } } }
/// <summary> /// Handles setting the process startup info from setup. /// We dont use a processtartinfo object since that would limit what a user could do. /// Unfortunately, that also means the user could easily break/expand its usage. /// </summary> /// <param name="process"></param> /// <param name="setup"></param> /// <returns></returns> protected virtual bool OnSetupProcess(Process process, ProcessSetup setup) { process.StartInfo.FileName = setup.Executable; if (!string.IsNullOrWhiteSpace(setup.WorkingDir)) { process.StartInfo.WorkingDirectory = setup.WorkingDir; } process.StartInfo.Arguments = setup.Arguments; process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; if (setup.EnvironmentVariables != null) { //TODO: Should we check existing environmentvariables before just writing ours? foreach (var kv in setup.EnvironmentVariables) { process.StartInfo.Environment.Add(kv.key, kv.value); } } return(true); }
/* * Also, add static helper functions for running external processes without needing to create a ProcessHandler. * */ #region Static Helper functions for running external processes. /// <summary> /// Static function for executing external processes without going thru an instance process. /// Note that this code path is not exactly the same as the instance ones. /// </summary> /// <param name="setup"></param> /// <param name="timeout">time to wait before killing process in ms.</param> /// <returns></returns> public static ProcessResult RunProcess(ProcessSetup setup, int timeout = 30000) { var result = new ProcessResult(); using (var process = new Process()) { SetupProcess(process, setup); // setup output handling. result.Output = new List <string>(); process.OutputDataReceived += (sender, args) => { if (args.Data != null) { result.Output.Add(args.Data); } }; process.StartInfo.RedirectStandardOutput = true; // setup error handling result.Errors = new List <string>(); process.ErrorDataReceived += (sender, args) => { if (args.Data != null) { result.Errors.Add(args.Data); } }; process.StartInfo.RedirectStandardError = true; // try to start process. try { result.WasStarted = process.Start(); } catch (Exception e) { // Usually it occurs when an executable file is not found or is not executable // or requires admin privileges. result.Errors.Add(e.Message); return(result); } // start listening for readlines. process.BeginOutputReadLine(); process.BeginErrorReadLine(); var flagTimedOut = process.WaitForExit(timeout); if (!flagTimedOut) { process.WaitForExit(); // wait more for redirect handlers to cmplete. } else { try { // kill hung process. process.Kill(); } catch { } // ignore error. } result.HasCompleted = true; result.ExitCode = process.ExitCode; return(result); } }
/// <summary> /// Run a process async with a timeout. /// </summary> /// <param name="setup"></param> /// <param name="timeout"></param> /// <returns></returns> public static async Task <ProcessResult> RunProcessAsync(ProcessSetup setup, int timeout) { var result = new ProcessResult(); using (var process = new Process()) { SetupProcess(process, setup); // setup output handling. result.Output = new List <string>(); var outputCloseEvent = new TaskCompletionSource <bool>(); process.OutputDataReceived += (sender, args) => { var d = args.Data; //if (string.IsNullOrWhiteSpace(d)) if (d == null) { outputCloseEvent.SetResult(true); } else { result.Output.Add(d); } }; process.StartInfo.RedirectStandardOutput = true; // setup error handling result.Errors = new List <string>(); var errorCloseEvent = new TaskCompletionSource <bool>(); process.ErrorDataReceived += (sender, args) => { var e = args.Data; if (e == null) { errorCloseEvent.SetResult(true); } else { result.Errors.Add(args.Data); } }; process.StartInfo.RedirectStandardError = true; // try to start process. try { result.WasStarted = process.Start(); } catch (Exception e) { // Usually it occurs when an executable file is not found or is not executable // or requires admin privileges. result.Errors.Add(e.Message); return(result); } if (result.WasStarted) { process.BeginOutputReadLine(); process.BeginErrorReadLine(); var taskWaitForExit = WaitForExitAsync(process, timeout); var taskProcess = Task.WhenAll(taskWaitForExit, outputCloseEvent.Task, errorCloseEvent.Task); if (await Task.WhenAny(Task.Delay(timeout), taskProcess) == taskProcess && taskWaitForExit.Result) { result.HasCompleted = true; result.ExitCode = process.ExitCode; } else { try { process.Kill(); } catch { } // ignore error. } } return(result); } }
/// <summary> /// Executes an external process async using provided processsetup and optinally a cancellation token. /// Note that this is not entirely the same code path as the instance version. /// </summary> /// <param name="setup"></param> /// <param name="cancel"></param> /// <returns></returns> public static async Task <ProcessResult> RunProcessAsync(ProcessSetup setup, CancellationToken cancel = default) { var result = new ProcessResult(); using (var process = new Process()) { SetupProcess(process, setup); // setup output handling. result.Output = new List <string>(); var outputCloseEvent = new TaskCompletionSource <bool>(); process.OutputDataReceived += (sender, args) => { var d = args.Data; //if (string.IsNullOrWhiteSpace(d)) if (d == null) { outputCloseEvent.SetResult(true); } else { result.Output.Add(d); } }; process.StartInfo.RedirectStandardOutput = true; // setup error handling result.Errors = new List <string>(); var errorCloseEvent = new TaskCompletionSource <bool>(); process.ErrorDataReceived += (sender, args) => { var e = args.Data; if (e == null) { errorCloseEvent.SetResult(true); } else { result.Errors.Add(args.Data); } }; process.StartInfo.RedirectStandardError = true; // setup exit handling var exitCloseEvent = new TaskCompletionSource <bool>(); process.Exited += (sender, args) => { exitCloseEvent.SetResult(true); }; process.EnableRaisingEvents = true; // try to start process. try { result.WasStarted = process.Start(); } catch (Exception e) { // Usually it occurs when an executable file is not found or is not executable // or requires admin privileges. result.Errors.Add(e.Message); return(result); } // start listening for readlines. process.BeginOutputReadLine(); process.BeginErrorReadLine(); // see instance method for explaining this code. // wrap all the event task into one task. var taskProcess = Task.WhenAll(exitCloseEvent.Task, outputCloseEvent.Task, errorCloseEvent.Task); // create a task for handling waiting for exit or cancellation whichever happens first. var taskWaitForExit = WaitForExitAsync(process, cancel); // this awaits any of event tasks and exit task. // when any is finished it waits for result from exit task anyway. if (await Task.WhenAny(taskProcess, taskWaitForExit) == taskProcess && taskWaitForExit.Result) { result.HasCompleted = true; result.ExitCode = process.ExitCode; } else { try { process.Kill(); } catch { } // ignore error. } return(result); } }
/// <summary> /// Implements executing an external process with overridable functions. /// </summary> /// <param name="setup"></param> /// <param name="timeout"></param> /// <returns></returns> public ProcessResult Execute(ProcessSetup setup, int timeout = 30000) { var result = new ProcessResult() { Output = new List <string>(), Errors = new List <string>() }; using (var process = new Process()) { if (!this.OnSetupProcess(process, setup)) { // handle erros. result.Errors.Add("OnSetupProcess returned false"); return(result); } // we assume user has handled setup correctly. process.OutputDataReceived += (sender, args) => { //TODO: should we just give user all output or filter? if (args.Data != null) { OnOutputReceived(result, args.Data); } }; process.StartInfo.RedirectStandardOutput = true; process.ErrorDataReceived += (sender, args) => { if (args.Data != null) { OnErrorReceived(result, args.Data); } }; process.StartInfo.RedirectStandardError = true; // try to start process. try { result.WasStarted = process.Start(); } catch (Exception e) { // Usually it occurs when an executable file is not found or is not executable // or requires admin privileges. result.Errors.Add(e.Message); return(result); } // start listening on readlines. process.BeginOutputReadLine(); process.BeginErrorReadLine(); var flagExitedBeforeTimeout = process.WaitForExit(timeout); if (flagExitedBeforeTimeout) { process.WaitForExit(); // wait more to ensure events are processed. result.HasCompleted = true; result.ExitCode = process.ExitCode; } else { // kill process. try { process.Kill(); } catch { } // ignore error. } //TODO: should we indicate to user that process was killed? return(result); } }
/// <summary> /// Implements executing an external process async with overridable functions. /// </summary> /// <param name="setup"></param> /// <param name="cancel"></param> /// <returns></returns> public async Task <ProcessResult> ExecuteAsync(ProcessSetup setup, CancellationToken cancel = default) { var result = new ProcessResult() { Output = new List <string>(), Errors = new List <string>() }; using (var process = new Process()) { if (!OnSetupProcess(process, setup)) { // handle erros. result.Errors.Add("OnSetupProcess returned false"); return(result); } // we assume user has handled setup correctly. // setup output event handling. var outputCloseEvent = new TaskCompletionSource <bool>(); process.OutputDataReceived += (sender, args) => { if (args.Data == null) { outputCloseEvent.SetResult(true); } else { OnOutputReceived(result, args.Data); } }; process.StartInfo.RedirectStandardOutput = true; // setup error event listening. var errorCloseEvent = new TaskCompletionSource <bool>(); process.ErrorDataReceived += (sender, args) => { if (args.Data == null) { errorCloseEvent.SetResult(true); } else { OnErrorReceived(result, args.Data); } }; process.StartInfo.RedirectStandardError = true; // setup exit event listening. var exitCloseEvent = new TaskCompletionSource <bool>(); process.Exited += (sender, args) => { exitCloseEvent.SetResult(true); }; process.EnableRaisingEvents = true; // try to start process. try { result.WasStarted = process.Start(); } catch (Exception e) { // Usually it occurs when an executable file is not found or is not executable // or requires admin privileges. result.Errors.Add(e.Message); return(result); } // start listening on readlines. process.BeginOutputReadLine(); process.BeginErrorReadLine(); //TODO: find out how to await a cancellation token. /* * There are now 2 types of tasks. * * Those that should all be finished. * * Those that can be either. * * So wrap the event tasks into one taskProcess which awaits all. * Then create a task which handles waiting and cancellation. * * At last, wait for which finished first. */ // wrap all the event task into one task. var taskProcess = Task.WhenAll(exitCloseEvent.Task, outputCloseEvent.Task, errorCloseEvent.Task); // create a task for handling waiting for exit or cancellation whichever happens first. var taskWaitForExit = OnWaitForExitAsync(process, cancel); // this awaits any of event tasks and exit task. // when any is finished it waits for result from exit task anyway. if (await Task.WhenAny(taskProcess, taskWaitForExit) == taskProcess && taskWaitForExit.Result) { result.ExitCode = process.ExitCode; result.HasCompleted = true; } else { // process canceled. kill it... try { process.Kill(); } catch { } // ignore error. } return(result); } }