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 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