Friday, September 25, 2015

Using Process.Start to capture console output


Copy paste from as note, allcredit belong there http://csharptest.net/532/using-processstart-to-capture-console-output/



Background: I get a lot of traffic looking for details on running a process and collecting the process output. If you haven’t already done so, you should read “How to use System.Diagnostics.Process correctly“. It outlines the major pitfalls of using this class. Another post on “Using the ProcessRunner class” will demonstrate using the helper class I wrote: CSharpTest.Net.Processes.ProcessRunner.
So again I find myself writing about using System.Diagnostics.Process and the Process.Start method. This time it’s not to provide any more information, but rather a simplified example. This example will build upon the previous two posts “How to search the environment’s path for an exe or dll“, and “How to correctly escape command line arguments” to use those methods for solving those needs. Otherwise this example is a relatively stand-alone method for running a process and capturing it’s output.
/// 
/// Runs the specified executable with the provided arguments and returns the process' exit code.
/// 
/// Recieves the output of either std/err or std/out
/// Provides the line-by-line input that will be written to std/in, null for empty
/// The executable to run, may be unqualified or contain environment variables
/// The list of unescaped arguments to provide to the executable
/// Returns process' exit code after the program exits
/// Raised when the exe was not found
/// Raised when one of the arguments is null
/// Raised if an argument contains '\0', '\r', or '\n'
public static int Run(Action output, TextReader input, string exe, params string[] args)
{
if (String.IsNullOrEmpty(exe))
throw new FileNotFoundException();
if (output == null)
throw new ArgumentNullException("output");
 
ProcessStartInfo psi = new ProcessStartInfo();
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.CreateNoWindow = true;
psi.ErrorDialog = false;
psi.WorkingDirectory = Environment.CurrentDirectory;
psi.FileName = FindExePath(exe); //see http://csharptest.net/?p=526
psi.Arguments = EscapeArguments(args); // see http://csharptest.net/?p=529
 
using (Process process = Process.Start(psi))
using (ManualResetEvent mreOut = new ManualResetEvent(false),
mreErr = new ManualResetEvent(false))
{
process.OutputDataReceived += (o, e) => { if (e.Data == null) mreOut.Set(); else output(e.Data); };
process.BeginOutputReadLine();
process.ErrorDataReceived += (o, e) => { if (e.Data == null) mreErr.Set(); else output(e.Data); };
process.BeginErrorReadLine();
 
string line;
while (input != null && null != (line = input.ReadLine()))
process.StandardInput.WriteLine(line);
 
process.StandardInput.Close();
process.WaitForExit();
 
mreOut.WaitOne();
mreErr.WaitOne();
return process.ExitCode;
}
}
Though most people won’t require writing to std::in, it is demonstrated here. If you do decide to remove this be careful that you still set psi.RedirectStandardInput to true, and call process.StandardInput.Close() before you call process.WaitForExit().
If you want to read a single character at a time (so as not to wait for a new line) that can also be done. Unfortunately it does require more work. The following sample class reads one character at a time and writes it to the console:
//In the above example you would remove the event subscription and the call to
//process.BeginOutputReadLine() and replace it with the following:
new ReadOutput(process.StandardInput, mreOut);
 
private class ReadOutput
{
private StreamReader _reader;
private ManualResetEvent _complete;
 
public ReadOutput(StreamReader reader, ManualResetEvent complete)
{
_reader = reader;
_complete = complete;
Thread t = new Thread(ReadAll);
t.Start();
}
 
void ReadAll()
{
int ch;
while(-1 != (ch = _reader.Read()))
{
Console.Write((char) ch);
}
_complete.Set();
}
}
Always remember you need two threads reading both std::out and std::err or you run into the possibility of hanging the process.