Here’s my stab at writing a function that uses multiple threads for reading from a C# Process object’s streams, StandardError and StandardOutput. I’ve provided a wrapper function RunShellCommand on top of RunProcess which is the most generic. If you don’t want to run as a different user, pass in a null or zero-length string for username.
Note that workingDirectory MUST be set to something valid if you run as a different user. Set to null if you want to run in current directory.
The reason for the lock() in GetOutput is that I want to create a function that outputs to one StringBuilder, not two. If you will always have two StringBuilders, then you can get rid of the lock.
I’m super-new to threads, so be kind for any obvious mistakes!
public void RunShellCommand(
string username
, string domain
, SecureString password
, string workingDirectory
, string command
, out string stdOut
, out string stdErr
, out int exitCode
, int timeoutSeconds)
{
string cmd = “/c” + command;
string fileName = System.Environment.ExpandEnvironmentVariables(“%comspec%”);
RunProcess(
username
, domain
, password
, workingDirectory
, fileName
, cmd
, out stdOut
, out stdErr
, out exitCode
, timeoutSeconds);
}
public void RunProcess(
string username
, string domain
, SecureString password
, string workingDirectory
, string fileName
, string arguments
, out string stdOut
, out string stdErr
, out int exitCode
, int timeoutSeconds)
{
Process p = new Process();
p.StartInfo.FileName = fileName;
p.StartInfo.Arguments = arguments;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WorkingDirectory = workingDirectory;
if (!string.IsNullOrEmpty(username))
{
p.StartInfo.UserName = username;
p.StartInfo.Domain = domain;
p.StartInfo.Password = password;
}
StringBuilder sbOut = new StringBuilder();
StringBuilder sbErr = new StringBuilder();
List<object> lOut = new List<object>();
List<object> lErr = new List<object>();
p.Start();
lOut.Add(p.StandardOutput);
lOut.Add(sbOut);
lErr.Add(p.StandardError);
lErr.Add(sbErr);
System.Threading.Thread outThread = new System.Threading.Thread(GetOutput);
System.Threading.Thread errThread = new System.Threading.Thread(GetOutput);
outThread.IsBackground = true;
errThread.IsBackground = true;
outThread.Start(lOut);
errThread.Start(lErr);
if (!p.WaitForExit(timeoutSeconds * 1000))
{
throw new ApplicationException(
string.Format(
“Executing {0} {1} timed out!”
, p.StartInfo.FileName
, p.StartInfo.Arguments));
}
//
// Wait for readers grab all data before closing streams.
// Closing the stream before it has finished may cause data-loss and may
// cause an ObjectDisposedException on the thread but this is trapped.
//
errThread.Join(timeoutSeconds * 1000);
outThread.Join(timeoutSeconds * 1000);
p.StandardError.Close();
p.StandardOutput.Close();
stdOut = sbOut.ToString();
stdErr = sbErr.ToString();
exitCode = p.ExitCode;
}
private void GetOutput(object parameters)
{
List<object> paramList = (List<object>)parameters;
StreamReader sr = (StreamReader)paramList[0];
StringBuilder sb = (StringBuilder)paramList[1];
try
{
while (!sr.EndOfStream)
{
string l = sr.ReadLine();
lock (sb)
{
sb.AppendLine(l);
}
Console.WriteLine(l);
}
}
// ObjectDisposedException happens if we timeout and close the
// stream before sr.EndOfStream is reached
catch (ObjectDisposedException) { }
}