Last active
          October 10, 2024 21:05 
        
      - 
      
 - 
        
Save AlexMAS/f2dc6c0527646fd0284f34dafdfcdc21 to your computer and use it in GitHub Desktop.  
    The right way to run external process in .NET
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | public static class ProcessHelper | |
| { | |
| public static ProcessResult ExecuteShellCommand(string command, string arguments, int timeout) | |
| { | |
| var result = new ProcessResult(); | |
| using (var process = new Process()) | |
| { | |
| process.StartInfo.FileName = command; | |
| process.StartInfo.Arguments = arguments; | |
| process.StartInfo.UseShellExecute = false; | |
| process.StartInfo.RedirectStandardInput = true; | |
| process.StartInfo.RedirectStandardOutput = true; | |
| process.StartInfo.RedirectStandardError = true; | |
| process.StartInfo.CreateNoWindow = true; | |
| var outputBuilder = new StringBuilder(); | |
| var errorBuilder = new StringBuilder(); | |
| using (var outputCloseEvent = new AutoResetEvent(false)) | |
| using (var errorCloseEvent = new AutoResetEvent(false)) | |
| { | |
| var copyOutputCloseEvent = outputCloseEvent; | |
| process.OutputDataReceived += (s, e) => | |
| { | |
| // Output stream is closed (process completed) | |
| if (string.IsNullOrEmpty(e.Data)) | |
| { | |
| copyOutputCloseEvent.Set(); | |
| } | |
| else | |
| { | |
| outputBuilder.AppendLine(e.Data); | |
| } | |
| }; | |
| var copyErrorCloseEvent = errorCloseEvent; | |
| process.ErrorDataReceived += (s, e) => | |
| { | |
| // Error stream is closed (process completed) | |
| if (string.IsNullOrEmpty(e.Data)) | |
| { | |
| copyErrorCloseEvent.Set(); | |
| } | |
| else | |
| { | |
| errorBuilder.AppendLine(e.Data); | |
| } | |
| }; | |
| bool isStarted; | |
| try | |
| { | |
| isStarted = process.Start(); | |
| } | |
| catch (Exception error) | |
| { | |
| result.Completed = true; | |
| result.ExitCode = -1; | |
| result.Output = error.Message; | |
| isStarted = false; | |
| } | |
| if (isStarted) | |
| { | |
| // Read the output stream first and then wait because deadlocks are possible | |
| process.BeginOutputReadLine(); | |
| process.BeginErrorReadLine(); | |
| if (process.WaitForExit(timeout) | |
| && outputCloseEvent.WaitOne(timeout) | |
| && errorCloseEvent.WaitOne(timeout)) | |
| { | |
| result.Completed = true; | |
| result.ExitCode = process.ExitCode; | |
| if (process.ExitCode != 0) | |
| { | |
| result.Output = $"{outputBuilder}{errorBuilder}"; | |
| } | |
| } | |
| else | |
| { | |
| try | |
| { | |
| // Kill hung process | |
| process.Kill(); | |
| } | |
| catch | |
| { | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| public struct ProcessResult | |
| { | |
| public bool Completed; | |
| public int? ExitCode; | |
| public string Output; | |
| } | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment