/// <summary> /// Gets called when the external process writes to the error pipe. /// </summary> /// <param name="result"></param> /// <param name="data"></param> protected virtual void OnErrorReceived(ProcessResult result, string data) { result.Errors.Add(data); }
/// <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> /// Gets called when the external process writes to the output pipe. /// </summary> /// <param name="result"></param> /// <param name="data"></param> protected virtual void OnOutputReceived(ProcessResult result, string data) { //TODO: this function might need more context parameters than result and string data... result.Output.Add(data); }
/// <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); } }