Process.StartInfo.RedirectStandardOutput: Not quite what you'd expect in DLLs or WinForms apps
While implementing an automation strategy I recently discovered a quirk in output redirection. In a C# you can start a process, wait for it to exit and gather its CLI output (StandardOut). In my testing I found that the “Console” type application handles this scenario flawlessly without any difficulties. However, if you have a DLL or WinForms project you need to take an extra step to ensure this works correctly and avoid these problems:
-
Started process appears to exit immediately
-
NULL output received as results of the process run
Update (14-Dec-2011): Added additional lines about StandardError
Notes:
-
Visual Studio 2010
-
.NET 4.0 and 3.5
-
NUnit v2.5.10
-
plink.exe v0.61
-
StackOverflow page I created and ended up answering myself 10 hours later
-
DLLs and WinForms apps need to have both StandardOutput AND StandardInput (And sometimesStandardError )Redirected for StandardOut Capture to work correctly (See below for example)
As a POC for an automation strategy I created a C# Console application which calls plink.exe and remotely executes a script against *NIX hosts:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Process process = new Process();
process.StartInfo.FileName = @"C:\Tools\plink.exe";
process.StartInfo.Arguments = @"10.10.9.27 -l root -pw PASSWORD -m C:\test.sh";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
output = output.Trim().ToLower(); // Output is successfully captured here
if (output == "pass")
{
Console.WriteLine("Passed!");
}
}
}
}
This Console app:
- Sets up a new process to redirect standard output to a variable named 'output'
- Calls plink.exe with arguments to login to the remote box and run a script
- Waits until the process completes
- Evaluates the Captured StandardOutput to see if the script executed correctly (The script will return either a 'pass' or a 'fail')
In My testing, it worked perfectly! So, I altered it to be driven as an NUnit DLL:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nunit.Framework;
namespace ClassLibrary1
{
[TestFixture]
public class InstallTest
{
[Test]
public void InstallAgentNix()
{
Process process = new Process();
process.StartInfo.FileName = @"C:\Tools\plink.exe";
process.StartInfo.Arguments = @"10.10.9.27 -l root -pw PASSWORD -m C:\test.sh";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
output = output.Trim().ToLower();
Assert.AreEqual("pass", output, "Test Case Failed");
}
}
}
When I fired up the NUnit GUI and ran the test, I noticed that it returned as failed within a couple of seconds and indicated that the variable output had a NULL value.
This didn't make any sense to me, as all the documentation I had read seemed to indicate that all you had to do was redirect the standard output of the process then wait for it to be completed.
After trying a few other things which didn't work, I created the StackOverflow question listed above in the notes and went home. When I got into work the next day nobody had answered it so I continued with my personal research and found that WinForms applications have the same issue!
With my new findings about WinForms I did some more googling and found a few pages with information that helped me understand what was going on:
- http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/86cfb618-bb5a-47fa-b186-5588c0cf1fbf/
- http://stackoverflow.com/questions/5591089/how-to-send-command-and-receive-data-in-command-prompt-in-c-sharp-gui-applicatio
- http://stackoverflow.com/questions/1910592/process-waitforexit-on-console-vs-windows-forms
It looks like in addition to redirecting the StandardOutput I also have to redirect the StandardInput- even if I'm not going to use it.
After rewriting the DLL to include the change to redirect StandardIn, my sample test worked perfectly when run through NUnit (Added lines in BOLD below):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nunit.Framework;
namespace ClassLibrary1
{
[TestFixture]
public class InstallTest
{
[Test]
public void InstallAgentNix()
{
Process process = new Process();
process.StartInfo.FileName = @"C:\Tools\plink.exe";
process.StartInfo.Arguments = @"10.10.9.27 -l root -pw PASSWORD -m C:\test.sh";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardError = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
output = output.Trim().ToLower();
Assert.AreEqual("pass", output, "Test Case Failed");
}
}
}
Note: Updated 14-December-2011 to reflect the occasional need for redirecting StandardError.