175

I need to spawn a child process that is a console application, and capture its output.

I wrote up the following code for a method:

string retMessage = String.Empty;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();

startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;

startInfo.UseShellExecute = false;
startInfo.Arguments = command;
startInfo.FileName = exec;

p.StartInfo = startInfo;
p.Start();

p.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        using (StreamReader output = p.StandardOutput)
        {
            retMessage = output.ReadToEnd();
        }
    }
);

p.WaitForExit();

return retMessage;

However, this does not return anything. I don't believe the OutputDataReceived event is being called back, or the WaitForExit() command may be blocking the thread so it will never callback.

Any advice?

EDIT: Looks like I was trying too hard with the callback. Doing:

return p.StandardOutput.ReadToEnd(); 

Appears to work fine.

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
FlySwat
  • 172,459
  • 74
  • 246
  • 311
  • 2
    If you aren't going to be interacting with the application and just care about its output, you should not use the Async `BeginOutputReadLine()` and `Start()` way of doing it. I have found these to be not very reliable, and they can sometimes truncate the beginning of the application's output. – Michael Graczyk Jul 16 '12 at 23:47
  • Why not `retMessage = e.Data`? Which is a string var already:) – Nickon Sep 02 '13 at 08:28

9 Answers9

187

Here's code that I've verified to work. I use it for spawning MSBuild and listening to its output:

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data);
process.Start();
process.BeginOutputReadLine();
Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
  • 42
    The crucial command that fixes the OP is adding BeginOutputReadLine() – Mark Lakata May 15 '12 at 22:16
  • 7
    I can't believe how many people leave out the "BeginOutputReadLine" part. Saved my days, thanks! – Eugen Timm Jul 17 '14 at 13:01
  • 12
    To also capture errors, add `RedirectStandardError = true` and `process.ErrorDataReceived += (sender, args) => Console.WriteLine(args.Data);` and `process.BeginErrorReadLine();` – magnusarinell Feb 29 '16 at 08:31
  • 12
    Curious, is there time between Start() and BeginOutputReadLine(), where output could be lost (not given to OutputDataReceived)? – Vimes May 11 '17 at 16:40
  • @Vimes, I don't believe so; the output is provided by the Process as a stream, so at the worst there would be a delay between output production and consumption. – timelmer Sep 18 '22 at 22:51
43

I just tried this very thing and the following worked for me:

StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;

outputBuilder = new StringBuilder();

processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.Arguments = "<insert command line arguments here>";
processStartInfo.FileName = "<insert tool path here>";

process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        // append the new data to the data already read-in
        outputBuilder.Append(e.Data);
    }
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();

// use the output
string output = outputBuilder.ToString();
Umar Farooq Khawaja
  • 3,925
  • 1
  • 31
  • 52
33

Here's some full and simple code to do this. This worked fine when I used it.

var processStartInfo = new ProcessStartInfo
{
    FileName = @"C:\SomeProgram",
    Arguments = "Arguments",
    RedirectStandardOutput = true,
    UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

Note that this only captures standard output; it doesn't capture standard error. If you want both, use this technique for each stream.

Community
  • 1
  • 1
Sam
  • 40,644
  • 36
  • 176
  • 219
29

I needed to capture both stdout and stderr and have it timeout if the process didn't exit when expected. I came up with this:

Process process = new Process();
StringBuilder outputStringBuilder = new StringBuilder();

try
{
process.StartInfo.FileName = exeFileName;
process.StartInfo.WorkingDirectory = args.ExeDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var processExited = process.WaitForExit(PROCESS_TIMEOUT);

if (processExited == false) // we timed out...
{
    process.Kill();
    throw new Exception("ERROR: Process took too long to finish");
}
else if (process.ExitCode != 0)
{
    var output = outputStringBuilder.ToString();
    var prefixMessage = "";

    throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine + 
    "Output from process: " + outputStringBuilder.ToString());
}
}
finally
{                
process.Close();
}

I am piping the stdout and stderr into the same string, but you could keep it separate if needed. It uses events, so it should handle them as they come (I believe). I have run this successfully, and will be volume testing it soon.

Robb Sadler
  • 705
  • 10
  • 22
21

It looks like two of your lines are out of order. You start the process before setting up an event handler to capture the output. It's possible the process is just finishing before the event handler is added.

Switch the lines like so.

p.OutputDataReceived += ...
p.Start();        
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • Even though its not marked as such, this is probably the right answer. – Casper Leon Nielsen Jan 25 '12 at 14:27
  • This did not work for me. The EDIT that FlySwat included in his answer worked for me. – Mark Lakata May 15 '12 at 22:15
  • @CasperLeonNielsen, ditto. – as9876 Apr 16 '15 at 03:24
  • 1
    This is wrong. To make this work, you'd need first to call `BeginOutputReadLine` which starts to read the stdOut stream. You could read the stream directly too. Unfortunately the [implementation](https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs,2159) creates the IO streams only once the process is [started](https://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs,1973). Calling `BeginOutputReadLine` or reading from the stream before calling `process.Start()` will throw because the stream does not exist yet. – 3dGrabber Dec 17 '19 at 10:16
4

Redirecting the stream is asynchronous and will potentially continue after the process has terminated. It is mentioned by Umar to cancel after process termination process.CancelOutputRead(). However that has data loss potential.

This is working reliably for me:

process.WaitForExit(...);
...
while (process.StandardOutput.EndOfStream == false)
{
    Thread.Sleep(100);
}

I didn't try this approach but I like the suggestion from Sly:

if (process.WaitForExit(timeout))
{
    process.WaitForExit();
}
jws
  • 2,171
  • 19
  • 30
  • 4
    When reading output / error streams asynchronously call process.WaitForExit() (the parameterless version) after process.WaitForExit(timeout) returned true. This will block until the end of output / error streams was reached. See the remarks here for details: http://msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29 – Sly Sep 10 '14 at 18:17
3

You need to call p.Start() to actually run the process after you set the StartInfo. As it is, your function is probably hanging on the WaitForExit() call because the process was never actually started.

SoapBox
  • 20,457
  • 3
  • 51
  • 87
2

The answer from Judah did not work for me (or is not complete) as the application was exiting after the first BeginOutputReadLine();

This works for me as a complete snippet, reading the constant output of a ping:

        var process = new Process();
        process.StartInfo.FileName = "ping";
        process.StartInfo.Arguments = "google.com -t";
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.UseShellExecute = false;
        process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data);
        process.Start();
        process.BeginOutputReadLine();
        process.WaitForExit();
Craig
  • 474
  • 7
  • 21
-1

Here's a method that I use to run a process and gets its output and errors :

public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments)
    {
        using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }))
        {
            using (process.StandardOutput)
            {
                writer.WriteLine(process.StandardOutput.ReadToEnd());
            }
            using (process.StandardError)
            {
                writer.WriteLine(process.StandardError.ReadToEnd());
            }
        }

        return path;
    }

For example :

@"E:\Temp\MyWorkingDirectory".ShellExecute(@"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out);
Beatles1692
  • 5,214
  • 34
  • 65
  • 11
    Isn't that a deadlock waiting to happen? MSDN Docs say that you risk a deadlock if listening to both output and error at the same time. The app will stop if the error buffer is full, and wait for it to empty. But you're not emptying the error buffer until the output buffer is finished (which it won't be as the app is waiting for the error buffer) ... – Michael Bisbjerg Mar 31 '13 at 15:42