ProcessInfo and RedirectStandardOutput - c#

I have an app which calls another process in a command window and that process has updating stats that output to the console window. I thought this was a fairly simple operation but I can't seem to get it to work. Am I missing something?
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
Process process = new Process
{
ProcessStart =
{
RedirectStandardOutput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = arg,
FileName = assemblyLocation.Substring(0, assemblyLocation.LastIndexOf("\\")) + "\\ffmpeg.exe",
CreateNoWindow = true
}
};
process.Start();
Console.WriteLine(process.StandardOutput.ReadToEnd());
process.WaitForExit();
Ideally what I would like is as the output changes within that process I hit or data comes into the reader that I get events off it.
Any help would be great, I feel like this is a newbie question but seem to be missing something.

I've experienced this before. Sometimes, the way in which the process you're calling outputs to the console is not compatible with this sort of output redirection. I've been fortunate enough in this case to be able to modify the external process to get around this.
You might try running your code on another process that outputs to the console, and see if it works properly. It reads about right to me right now.
EDIT:
I went and pulled a code block I've used to do this. This is in a WPF app which redirects the process output to the window. Notice the event binding. Since this is WPF I have to invoke my call to write the data out. Since you aren't worried about blocking, ou should be able to simply replace that with:
Console.WriteLine(e.Data);
Hopefully it helps!
private static void LaunchProcess()
{
Process build = new Process();
build.StartInfo.WorkingDirectory = #"dir";
build.StartInfo.Arguments = "";
build.StartInfo.FileName = "my.exe";
build.StartInfo.UseShellExecute = false;
build.StartInfo.RedirectStandardOutput = true;
build.StartInfo.RedirectStandardError = true;
build.StartInfo.CreateNoWindow = true;
build.ErrorDataReceived += build_ErrorDataReceived;
build.OutputDataReceived += build_ErrorDataReceived;
build.EnableRaisingEvents = true;
build.Start();
build.BeginOutputReadLine();
build.BeginErrorReadLine();
build.WaitForExit();
}
// write out info to the display window
static void build_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
string strMessage = e.Data;
if (richTextBox != null && !String.Empty(strMessage))
{
App.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Send, (ThreadStart)delegate()
{
Paragraph para = new Paragraph(new Run(strMessage));
para.Margin = new Thickness(0);
para.Background = brushErrorBrush;
box.Document.Blocks.Add(para);
});
}
}

I'm not sure exactly what problem you're running into, but if you're looking to act on output as soon as it's generated, try hooking into the process's OutputDataReceived event. You can specify handlers to receive output asynchronously from the process. I've used this approach successfully.
Process p = new Process();
ProcessStartInfo info = p.info;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
p.OutputDataReceived += p_OutputDataReceived;
p.ErrorDataReceived += p_ErrorDataReceived;
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
..
void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard out: " + e.Data);
}
void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Received from standard error: " + e.Data);
}
See the OutputDataReceived event off Process for more information.

Using lambda expressions, etc:
var info = new ProcessStartInfo(path)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
Verb = "runas",
};
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = info
};
Action<object, DataReceivedEventArgs> actionWrite = (sender, e) =>
{
Console.WriteLine(e.Data);
};
process.ErrorDataReceived += (sender, e) => actionWrite(sender, e);
process.OutputDataReceived += (sender, e) => actionWrite(sender, e);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();

Interestingly you can't read from standard output and standard error at the same time:
if you redirect both standard output and standard error and then try to read both, for
example using the following C# code.
[C#]
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
In this case, if the child process writes any text to standard error it will block the
process, because the parent process cannot read from standard error until it has finished
reading from standard output. However, the parent process will not read from standard
output until the process ends. A recommended solution to this situation is to create two
threads so that your application can read the output of each stream on a separate thread.
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardoutput(v=vs.71).aspx

flowing code worked in VS2010
void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data) == false)
{
new Thread(() =>
{
this.Dispatcher.Invoke(new Action(() =>
{
// Add you code here
}));
}).Start();
}
}

Check that the output you are expecting is not being sent to the StandardError output instead of the StandardOutput output

Related

Executing batch file from C# winforms ignores timeout

All,
I am attempting to execute a series of batch files via a C# winforms app. In this early stage, with a test batch file, I am unable to get the process execution to respect the timeout in my batch file unless i set UseShellExecute = true, which is something i am trying to avoid. My goal is to execute the script file and redirect the output to the GUI as shown in the code here:
Process process;
public void ExecuteScript(string workingDirectory, string batchFileName)
{
if (process != null)
process.Dispose();
process = new Process();
process.StartInfo.WorkingDirectory = workingDirectory;
process.StartInfo.FileName = workingDirectory + batchFileName;
process.StartInfo.Arguments = "";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += proc_OutputDataReceived;
process.Start();
process.BeginOutputReadLine();
process.Exited += OnProcessExit;
}
private void OnProcessExit(object sender, EventArgs e)
{
Console.WriteLine("the script has ended");
}
private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.Invoke((Action)(() =>
{
textBox1.AppendText(Environment.NewLine + e.Data);
}));
(sender as Process)?.StandardInput.WriteLine();
}
my batch file looks like this:
#echo off
echo This is a running script
timeout /t 10
echo Done sleeping. Will Exit
exit
Is there an appropriate combination of settings i can call to prevent the command window from appearing, while still redirecting the output, and executing the script appropriately?
The problem with your code is that the timeout command is not supported when stdin is redirected. This is a good example of why one should always redirect both stdout and stderr. An error message is actually emitted from the batch file, but because you weren't capturing the stderr stream, you didn't see the error message. All too many questions here on Stack Overflow involving Process scenarios that "don't work" could be easily solved had the person looked at the stderr output.
A work-around to this limitation of the timeout command is to use the waitfor command instead, using a known-nonexistent signal name with a timeout value, e.g. waitfor /t 10 placeholder.
Here is a console program that is entirely self-contained and which demonstrates both the failure of the timeout command when stdin is redirected, as well as the work-around of waitfor:
const string batchFileText =
#"#echo off
echo Starting batch file
timeout /t 5
waitfor /t 5 placeholder
echo Timeout completed
exit";
static void Main(string[] args)
{
string batchFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".bat");
try
{
File.WriteAllText(batchFileName, batchFileText);
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = batchFileName,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
};
Process process = new Process
{
EnableRaisingEvents = true,
};
process.OutputDataReceived += Process_OutputDataReceived;
process.ErrorDataReceived += Process_ErrorDataReceived;
process.Exited += Process_Exited;
process.StartInfo = psi;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
finally
{
File.Delete(batchFileName);
}
}
private static void Process_Exited(object sender, EventArgs e)
{
WriteLine("Process exited");
}
private static void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stdout: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
private static void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stderr: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
Note that the waitfor command writes a message to stderr if the timeout occurs (which it always will in this case). You may or may not want that to show up in the captured stderr stream. If not, you can redirect the stderr of that command specifically by using 2>nul. E.g. waitfor /t 10 placeholder 2>nul.

Windows Service read cmd lines with eventhandler

I'm trying to run a command in the command prompt and run it as a service. This command starts the queue listener from Laravel. I want to run it as a service so this queue listener always runs in the background. When the listener outputs some lines I want to capture these and send an email. I already tried my code running it as a process and it's working, but when I try to run the code as a service it doesn't start.
Process process();
protected override void OnStart(string[] args)
{
process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.Arguments = "/C php artisan queue:listen --tries=3 --timeout=0 --memory=1024";
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.WorkingDirectory = "C:/xampp/htdocs/phpproject";
process.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
}
private void p_OutputDataReceived(object sendingProcess,
DataReceivedEventArgs outLine)
{
// Collect the command output.
if (!String.IsNullOrEmpty(outLine.Data.ToString()))
{
sendMail(process.StandardOutput.ToString());
}
}
Edit:
When I comment the process.WaitForExit() line the service runs and the queue listener does its work!.
But the next problem I have is the service never hits the eventhandler. It did when I was running it only as a process. Any clue why this isn't working as a service?
You can try something like this;
private Process process = null;
private DataReceivedEventHandler TheDataReceievedEventHandler;
private void startProcess()
{
ProcessStartInfo processStartInfo = new ProcessStartInfo(#"cmd.exe", #"/C php artisan queue:listen --tries=3 --timeout=0 --memory=1024")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = #"C:/xampp/htdocs/phpproject",
};
if ((process = Process.Start(processStartInfo)) != null)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(ExitedHandler);
TheDataReceievedEventHandler = new DataReceivedEventHandler(StandardOutputHandler);
process.OutputDataReceived += TheDataReceievedEventHandler;
process.BeginOutputReadLine();
process.ErrorDataReceived += TheDataReceievedEventHandler;
process.BeginErrorReadLine();
}
}
private void ExitedHandler(object sender, EventArgs e)
{
throw new NotImplementedException(); // the service you're trying to run closed it self.
}
private void StandardOutputHandler(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
The problem is if the service you are trying to run closes it self, it will stop outputting and the process will be closed. Similar to CMD behavior each time you send a command to CMD it will close it self after you receive the error or output.
So if for example; I want to use CMD to see my task-list, i will have to build a loop were i run a CMD process on a regular interval because after each command it will close it self.
Edit
If you can't stop the program using its own logic you will need to kill the process of the program itself. You can do that using the following code;
try // If you have no administrator privilege, try will fire.
{
foreach (Process proc in Process.GetProcessesByName("name process")) // You can get the name by looking in your task manager.
{
proc.Kill();
}
}
catch(Exception ex)
{
// Add error handling
}

Can't get output from cmd

I'm running this
string path = string.Format(#"\\{0}\c$\Windows\CCM\Logs", computerName);
Process process = Process.Start(new ProcessStartInfo()
{
FileName = "cmd.exe",
Arguments = string.Format(#"net use {0} && dir {0}", path),
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
});
string result = process.StandardOutput.ReadToEnd() + " " + process.StandardError.ReadToEnd();
process.WaitForExit();
Console.WriteLine(result);
But nothing is ever written to the console. What am I doing wrong? I've browsed probably every other SO thread regarding this and done a fair amount of Googling but I can't get it to work.
You need to use the /C option to cmd.exe otherwise the child process won't exit.
/C Carries out the command specified by string and then terminates
(Type cmd /? in your command prompt for more information)
RedirectStandardOutput = true; and RedirectStandardError = true; will redirect respective streams. To capture those streams, you need to handle OutputDataReceived event as follows:
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
process.BeginOutputReadLine();
I´m using the following code to print out the StandardError and StandardOutput of a Process to the Debug/Console
using (StreamReader reader = process.StandardError) {
string result = reader.ReadToEnd();
System.Diagnostics.Debug.Write(result);
}
using (StreamReader reader = process.StandardOutput) {
string result = reader.ReadToEnd();
System.Diagnostics.Debug.Write(result);
}
process.WaitForExit();
Also i set the following properties on StartInfo
StartInfo.UseShellExecute = false;
StartInfo.ErrorDialog = false;
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardError = true;
I think you're facing a deadlock as described in the documentation:
A deadlock condition results if the parent process calls p.StandardOutput.ReadToEnd followed by p.StandardError.ReadToEnd and the child process writes enough text to fill its error stream. The parent process would wait indefinitely for the child process to close its StandardOutput stream. The child process would wait indefinitely for the parent to read from the full StandardError stream.
To avoid this you should use asynchronous read operation on one of the streams:
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
Courtesy should go to MSDN documentation.
i know this is old, but i wanted to share my solution when i came across this thread. No answer suited my needs. I didn't want read output at end of process. So this is the solution i came up with.
The solution solves both fast and slow response and so will always get all the output.
...
process = Process.Start(processInfo);
process.ErrorDataReceived += (sender, eargs) =>
{
// do something
};
process.OutputDataReceived += (sender, eargs) =>
{
// do something
};
if (timeout > 0)
{
// if it is a slow process, read async
if (!process.WaitForExit(200))
{
process.BeginOutputReadLine();
process.BeginErrorReadLine();
if (!process.WaitForExit(timeout))
{
// show error
return;
}
} else
{
// if it is fast process, need to use "ReadToEnd", because async read will not
// caputure output
var text = process.StandardOutput.ReadToEnd();
// do something with text
}
exitCode = process.ExitCode;
}

RedirectStandard Output/Error wont call my event for Standard Output/Error

hi i'm trying to build a parser for my System to Manage my Tekkit Server i am using C# but i have RedirectStandardOutput on my Tekkit Server process and there is a method set-up to then send that output to my console after adding to a List but it's not adding to a List<string>
Here is my code:
public void StartServer(string maxMem, string minMem, string path)
{
ThreadStart server = new ThreadStart(delegate() { StartServerThread(maxMem, minMem, path); });
server.Invoke();
}
private void StartServerThread(string maxMem, string minMem, string TekkitPath)
{
try
{
TekkitServer.StartInfo.FileName = "java";
TekkitServer.StartInfo.Arguments = String.Format("-Xmx{0} -Xms{1} -jar \"" + TekkitPath + "\" -nojline nogui", maxMem, minMem);
TekkitServer.StartInfo.UseShellExecute = false;
TekkitServer.StartInfo.RedirectStandardInput = true;
TekkitServer.StartInfo.RedirectStandardOutput = true;
TekkitServer.OutputDataReceived += new DataReceivedEventHandler(TekkitServer_OutputDataReceived);
IsStarted = TekkitServer.Start();
TekkitServerInput = TekkitServer.StandardInput;
}
catch (Exception)
{
}
}
void TekkitServer_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
/*B*/recordedData.Add(e.Data);
Console.Out.WriteLine(e.Data);
}
Where /*B*/ is a break point, the breakpoint is never activating
By default, the standard output is directed at the console window.
If you need to do something with it, you need to redirect it, hence, you need to set RedirectStandardOutput = true; for the event to be fired.
Edit: This is my working code (with error handling and logging omitted):
public int ExecuteCommand(CommandParameters parameters)
{
Process process = new Process();
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.OutputDataReceived += StdOutputHandler;
process.ErrorDataReceived += StdErrorHandler;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = ...;
process.StartInfo.Arguments = ...;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit(parameters.Timeout);
return process.ExitCode;
}
private void StdOutputHandler(object sendingProcess, DataReceivedEventArgs outdata)
{
if (!string.IsNullOrEmpty(outdata.Data))
{
OutputMessages.Add(outdata.Data);
}
}
Most likely the missing link in your code is the BeginOutputReadLine that actually gets the handler method on it's way.
Also, I use a fresh Process object and that I wait on it to finish it's job, so no interference with previous calls is possible.

StandardOutput.ReadToEnd() hangs [duplicate]

This question already has answers here:
ProcessStartInfo hanging on "WaitForExit"? Why?
(22 answers)
Closed 5 years ago.
I have a program that frequently uses an external program and reads its outputs.
It works pretty well using your usual process redirect output, but one specific argument for some reason hangs when I try to read it, no error message - no exception, it just 'stops' when it reaches that line.
I of course use a centralized function to call and read output from the program, which is this:
public string ADBShell(string adbInput)
{
try
{
//Create Empty values
string result = string.Empty;
string error = string.Empty;
string output = string.Empty;
System.Diagnostics.ProcessStartInfo procStartInfo
= new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe");
procStartInfo.Arguments = adbInput;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
procStartInfo.WorkingDirectory = toolPath;
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
// Get the output into a string
proc.WaitForExit();
result = proc.StandardOutput.ReadToEnd();
error = proc.StandardError.ReadToEnd(); //Some ADB outputs use this
if (result.Length > 1)
{
output += result;
}
if (error.Length > 1)
{
output += error;
}
Return output;
}
catch (Exception objException)
{
throw objException;
}
}
The line that hangs is result = proc.StandardOutput.ReadToEnd();, but again, not every time, only when sent a specific argument ("start-server"). All other arguments work just fine - it reads the value and returns it.
It's also strange the way it hangs. It doesn't freeze or give an error or anything, it just stops processing. As if it was a 'return' command, except it doesn't even return to the calling function, it just stops everything with the interface still up and running.
Anyone experienced this before? Anyone have any idea what I should try? I'm assuming it's something unexpected within the stream itself, but is there a way I can handle/ignore this so that it reads it anyway?
Proposed solutions with BeginOutputReadLine() are a good way but in situations such as that, it is not applicable, because process (certainly with using WaitForExit()) exits earlier than async output finished completely.
So, I tried to implement it synchronously and found that the solution is in using Peek() method from StreamReader class. I added check for Peek() > -1 to sure that it is not the end of the stream as in MSDN article described and finally it works and stop hanging!
Here is the code:
var process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = #"C:\test\";
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "your arguments here";
process.Start();
var output = new List<string>();
while (process.StandardOutput.Peek() > -1)
{
output.Add(process.StandardOutput.ReadLine());
}
while (process.StandardError.Peek() > -1)
{
output.Add(process.StandardError.ReadLine());
}
process.WaitForExit();
The problem is that you are using the synchronous ReadToEnd methods on both the StandardOutput and the StandardError streams. This can lead to a potential deadlock you are experiencing. This is even described in the MSDN. The solution is described there. Basically, it is: Use the asynchronous version BeginOutputReadLine to read the data of the StandardOutput stream:
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
Implementation of Async reading using BeginOutputReadLine see in ProcessStartInfo hanging on "WaitForExit"? Why?
What about something like:
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.OutputDataReceived += (sender, args) =>
{
var outputData = args.Data;
// ...
};
process.ErrorDataReceived += (sender, args) =>
{
var errorData = args.Data;
// ...
};
process.WaitForExit();
I had the same deadlock problem. This code snippet worked for me.
ProcessStartInfo startInfo = new ProcessStartInfo("cmd")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
process.StandardInput.WriteLine("echo hi");
process.StandardInput.WriteLine("exit");
var output = process.StandardOutput.ReadToEnd();
process.Dispose();
Something that is elegant and worked for me is:
Process nslookup = new Process()
{
StartInfo = new ProcessStartInfo("nslookup")
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
};
nslookup.Start();
nslookup.StandardInput.WriteLine("set type=srv");
nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local");
nslookup.StandardInput.Flush();
nslookup.StandardInput.Close();
string output = nslookup.StandardOutput.ReadToEnd();
nslookup.WaitForExit();
nslookup.Close();
This answer I found here and the trick is using Flush() and Close() on standard input.
The accepted answer's solution didn't work for me. I had to use tasks in order to avoid the deadlock:
//Code to start process here
String outputResult = GetStreamOutput(process.StandardOutput);
String errorResult = GetStreamOutput(process.StandardError);
process.WaitForExit();
With a GetStreamOutput function as follows:
private string GetStreamOutput(StreamReader stream)
{
//Read output in separate task to avoid deadlocks
var outputReadTask = Task.Run(() => stream.ReadToEnd());
return outputReadTask.Result;
}
I had the same kind of problem that error was just hanging.
Based on your response to Daniel Hilgarth I didn't even try using those codes though i think they would have worked for me.
Since I want to be able do some fancier output still eventually i decided that I would do it with both of the outputs being done in a background thread.
public static class RunCommands
{
#region Outputs Property
private static object _outputsLockObject;
private static object OutputsLockObject
{
get
{
if (_outputsLockObject == null)
Interlocked.CompareExchange(ref _outputsLockObject, new object(), null);
return _outputsLockObject;
}
}
private static Dictionary<object, CommandOutput> _outputs;
private static Dictionary<object, CommandOutput> Outputs
{
get
{
if (_outputs != null)
return _outputs;
lock (OutputsLockObject)
{
_outputs = new Dictionary<object, CommandOutput>();
}
return _outputs;
}
}
#endregion
public static string GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true)
{
// Redirect the output stream of the child process.
info.UseShellExecute = false;
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
var process = new Process();
process.StartInfo = info;
process.ErrorDataReceived += ErrorDataHandler;
process.OutputDataReceived += OutputDataHandler;
var output = new CommandOutput();
Outputs.Add(process, output);
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// Wait for the process to finish reading from error and output before it is finished
process.WaitForExit();
Outputs.Remove(process);
if (returnErrorIfPopulated && (!String.IsNullOrWhiteSpace(output.Error)))
{
return output.Error.TrimEnd('\n');
}
return output.Output.TrimEnd('\n');
}
private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine)
{
if (errLine.Data == null)
return;
if (!Outputs.ContainsKey(sendingProcess))
return;
var commandOutput = Outputs[sendingProcess];
commandOutput.Error = commandOutput.Error + errLine.Data + "\n";
}
private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine)
{
if (outputLine.Data == null)
return;
if (!Outputs.ContainsKey(sendingProcess))
return;
var commandOutput = Outputs[sendingProcess];
commandOutput.Output = commandOutput.Output + outputLine.Data + "\n";
}
}
public class CommandOutput
{
public string Error { get; set; }
public string Output { get; set; }
public CommandOutput()
{
Error = "";
Output = "";
}
}
This worked for me and allowed me to not have to use a timeout for the read.
Just in case someone stumbles upon this question while wiling to use Windows Forms and TextBox (or RichTextBox) to show the errors and outputs the process returns in real time (as they are written to process.StandardOutput / process.StandardError).
You need to use OutputDataReceived() / ErrorDataReceived() in order to read both streams without deadlocks, there is no way (as far as I know) to avoid deadlocks otherwise, even Fedor's answer, which now holds the "Answer" tag and the most likes up to date, does not do the trick for me.
However, when you use the RichTextBox (or TextBox) to output the data, another problem you encounter is how to actually write the data into the textbox in real time (once it arrives). You receive the access to the data inside one of the background threads OutputDataReceived() / ErrorDataReceived() and you can only AppendText() from the main thread.
What I first tried doing was calling process.Start() from a background thread and then calling BeginInvoke() => AppendText() in OutputDataReceived() / ErrorDataReceived() threads while the main thread was process.WaitForExit().
However, this led to my form freezing and ultimately hanging for eternity. After a few days of trying I ended up with the solution below, that seems to work pretty well.
Shortly speaking, you need to add the messages into a concurrent collection inside OutputDataReceived() / ErrorDataReceived() threads while the main thread should constantly try to extract messages from that collection and append them into the textbox:
ProcessStartInfo startInfo
= new ProcessStartInfo(File, mysqldumpCommand);
process.StartInfo.FileName = File;
process.StartInfo.Arguments = mysqldumpCommand;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.RedirectStandardInput = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
process.EnableRaisingEvents = true;
ConcurrentQueue<string> messages = new ConcurrentQueue<string>();
process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) =>
{
string data = ar.Data;
if (!string.IsNullOrWhiteSpace(data))
messages.Enqueue(data);
};
process.OutputDataReceived += (object se, DataReceivedEventArgs ar) =>
{
string data = ar.Data;
if (!string.IsNullOrWhiteSpace(data))
messages.Enqueue(data);
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
while (!process.HasExited)
{
string data = null;
if (messages.TryDequeue(out data))
UpdateOutputText(data, tbOutput);
Thread.Sleep(5);
}
process.WaitForExit();
The only downside of this approach is the fact that you can loose messages in a quite rare case, when process starts writing them between process.Start() and process.BeginErrorReadLine() / process.BeginOutputReadLine(), just keep that in mind. The only way to avoid that is to read the full streams and (or) gain access to them only when the process finishes.
first
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "Write500Lines.exe";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
second
// Do not perform a synchronous read to the end of both
// redirected streams.
// string output = p.StandardOutput.ReadToEnd();
// string error = p.StandardError.ReadToEnd();
// p.WaitForExit();
// Use asynchronous read operations on at least one of the streams.
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
This is from MSDN

Categories