Scott Hanselman

So many mistakes for me to make, so little time...capturing StandardError and StandardOutput

July 02, 2003 Comment on this post [1] Posted in Web Services | Bugs
Sponsored By

This struck me as particularly interesting, because it's one of those "doh!" things that is simultaneously completely obvious after the fact but not obvious on a cursory glance if you're not paying attention.

If, in C#/.NET, you wish to spawn a process of CMD.EXE /C SomeBatchFileOrSomeProgram.exe, and you wish to capture both the StandardOutput and the StandardError, well, you need to think.

If you do something like this:

Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.FileName = "test.exe";
p.Start();
string output = p.StandardError.ReadToEnd();
p.WaitForExit();

You'll only get StandardError.  But of course, if you try to ReadToEnd both streams (here's where it's obvious) the first one will of course block if standard error output occurs, since you can't read from standard error until you're done with standard output, etc etc, blah blah, well you get it. 

string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();  

There are lots of code samples around that contain this bug.  Of course, the bug won't happen if your spawned console process doesn't mix standard error and standard output.  But, when you spawn a batch file, certainly it will spawn other processes and who knows. 

This fellow at CodeProject has the right idea, although his sample wasn't created to solve this problem, but rather another.

Process Proc = Process.Start(Info);
   
m_StdOutReader = new RedirOutReader(Proc, Printer, Proc.StandardOutput);
m_StdOutReader.Start();
   
m_StdErrReader = new RedirOutReader(Proc, Printer, Proc.StandardError);

m_StdErrReader.Start();  

He spawns two threads, one to handle standard error and one to handle standard output, which is the alluded-to-and-recommended-but-no-one-wrote-the-sample-cause-it-would-have-been-too-hard solution to this problem. 

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
April 07, 2004 8:17
This morning I was faced with this issue and could not find a cooked up solution (was wishing I would).
Ended up writing a class to do this. Thought I would share it with folks.
Realized that you were one of the speakers as VSLive (I have to send you details for the other problem I had mentioned to you about the windows service running on console).
Anyways, here is the class:

using System;
using System.Diagnostics;
using System.Threading;

public class ProcessStream
{
/*
* Class to get process stdout/stderr streams
* Author: SeemabK (seemabk@yahoo.com)
* Usage:
//create ProcessStream
ProcessStream myProcessStream = new ProcessStream();
//create and populate Process as needed
Process myProcess = new Process();
myProcess.StartInfo.FileName = "myexec.exe";
myProcess.StartInfo.Arguments = "-myargs";

//redirect stdout and/or stderr
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;

//start Process
myProcess.Start();
//connect to ProcessStream
myProcessStream.Read(ref myProcess);
//wait for Process to end
myProcess.WaitForExit();

//get the captured output :)
string output = myProcessStream.StandardOutput;
string error = myProcessStream.StandardError;
*/

private Thread StandardOutputReader;
private Thread StandardErrorReader;
private static Process RunProcess;

private string _StandardOutput = "";
public string StandardOutput
{
get {return _StandardOutput;}
}
private string _StandardError = "";
public string StandardError
{
get {return _StandardError;}
}

public ProcessStream ()
{
Init();
}

public int Read (ref Process process)
{
try
{
Init();
RunProcess = process;

if (RunProcess.StartInfo.RedirectStandardOutput)
{
StandardOutputReader = new Thread(new ThreadStart(ReadStandardOutput));
StandardOutputReader.Start();
}
if (RunProcess.StartInfo.RedirectStandardError)
{
StandardErrorReader = new Thread(new ThreadStart(ReadStandardError));
StandardErrorReader.Start();
}

//RunProcess.WaitForExit();
if (StandardOutputReader != null)
StandardOutputReader.Join();
if (StandardErrorReader != null)
StandardErrorReader.Join();
}
catch
{}

return 1;
}

private void ReadStandardOutput ()
{
if (RunProcess != null)
_StandardOutput = RunProcess.StandardOutput.ReadToEnd();
}

private void ReadStandardError ()
{
if (RunProcess != null)
_StandardError = RunProcess.StandardError.ReadToEnd();
}

private int Init ()
{
_StandardError = "";
_StandardOutput = "";
RunProcess = null;
Stop();
return 1;
}

public int Stop ()
{
try {StandardOutputReader.Abort();}
catch {}
try {StandardErrorReader.Abort();}
catch {}
StandardOutputReader = null;
StandardErrorReader = null;
return 1;
}
}

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.