The Process class is useful any time you need to spawn off a child process. Sometimes you don’t really need to couple the parent process too tightly with the child process, but other times you may need to directly tie in to the standard input/output and manipulate the child process. One of the recurring gripes I hear from people that have had to capture output from another process using the Process class is that it seems to randomly fail to capture all the output. I have been struggling with this issue causing fluke crashes for a couple of weeks, but I’m happy to report I have found a solution (thanks go to James Kolpack for the suggestion).
Suppose I have some process Foo.exe that I want to run and capture output from.
Here’s how I would do it "the old way":
1: //Assume pathToFoo and myWorkingDirectory have been initialized elsewhere.
2:
3: ProcessStartInfo fooInfo = new ProcessStartInfo();
4: fooInfo.FileName = pathToFoo;
5: fooInfo.WorkingDirectory = myWorkingDirectory;
6: fooInfo.Arguments = string.Format("-arg1 {0} -arg2 {1}", arg1, arg2);
7: fooInfo.UseShellExecute = false;
8: fooInfo.RedirectStandardOutput = true;
9: fooInfo.CreateNoWindow = true;
10:
11: //Now that everything is in place and configured, run See5...
12: Process fooProcess;
13:
14: fooProcess = Process.Start(fooInfo);
15:
16: while (!fooProcess.StandardOutput.EndOfStream)
17: {
18: string line = fooProcess.StandardOutput.ReadToEnd();
19: //TODO: Process line!
20: }
21:
22: fooProcess.WaitForExit();
Let’s look at what’s going on here. First, we build a ProcessStartInfo object to run Foo.exe, and we pass in certain arguments to it. We tell it not to create a window, and we ask it to redirect standard output. This will allow us to read the stream from within our parent application. We could capture the standard error stream just as we have the standard output if we wanted to.
99.9% of the time, this code will work just fine. That remaining 0.1% of the time though, something will happen (I still don’t know what), and our parent process will stop reading standard output from the child. Sometimes the child will seem to exit before it actually completes (I’ve confirmed this behavior by having the child process output a file when it exists, which allowed me to verify that the process was going away unexpectedly). Other times, it will be like the child just stopped writing to standard output. I probably tried a few dozen different things to squash this bug, all of which failed. Finally, James suggested a rather simple solution that works perfectly: have the child process redirect to a file instead, then read the file back in.
Big note here: This solutions will *NOT* work if you need to interact with the process; it will only work if you just need to capture the output after it has finished running. If you need to interact with the process, you might as well get used to the glitchy behavior of Process, because I’ve yet to find another way to resolve this.
So, what do we need to change? The Process class won’t let us redirect to a file directly, but we can use the cmd.exe process as a wrapper for the process we want to invoked, then have cmd.exe redirect the output to a file for us. We can then read that file back in once the process exists. Here’s how:
1: //Assume pathToFoo and myWorkingDirectory have been initialized elsewhere.
2:
3: //Also assume that myTempFile contains the name of a temporary file that we'll clean up later.
4:
5: ProcessStartInfo fooInfo = new ProcessStartInfo();
6: fooInfo.FileName = "cmd.exe";
7: fooInfo.WorkingDirectory = myWorkingDirectory;
8: fooInfo.Arguments = string.Format("/c \"{0}\" -arg1 {1} -arg2 {2} > {3}", pathToFoo, arg1, arg2, myTempFile);
9:
10:
11: fooInfo.UseShellExecute = false;
12: fooInfo.CreateNoWindow = true;
13:
14: Process fooProcess;
15:
16: fooProcess = Process.Start(fooInfo);
17:
18: fooProcess.WaitForExit();
19:
20: string output = File.ReadAllText(Path.Combine(myWorkingDirectory, myTempFile));
21:
22: //TODO: Process the output
23: File.Delete(myTempFile);
As you can see, we are now running cmd.exe, and the path to Foo.exe has become its first parameter. Just as before, two arguments are passed to Foo.exe. Finally, we use the Arguments to instruct cmd.exe to redirect standard output to a temporay file. Note that we’re no longer redirecting standard output at the ProcessStartInfo-level, and that there is no loop to read from the process. Instead, we simply wait for the process to exit, then grab the output from the temporary file.
This solution works, but as I mentioned above, it isn’t perfect. If anyone has any suggestions, I’d love to hear them!