Redirect Standard Output Efficiently in .NET - c#

I am trying to call php-cgi.exe from a .NET program. I use RedirectStandardOutput to get the output back as a stream but the whole thing is very slow.
Do you have any idea on how I can make that faster? Any other technique?
Dim oCGI As ProcessStartInfo = New ProcessStartInfo()
oCGI.WorkingDirectory = "C:\Program Files\Application\php"
oCGI.FileName = "php-cgi.exe"
oCGI.RedirectStandardOutput = True
oCGI.RedirectStandardInput = True
oCGI.UseShellExecute = False
oCGI.CreateNoWindow = True
Dim oProcess As Process = New Process()
oProcess.StartInfo = oCGI
oProcess.Start()
oProcess.StandardOutput.ReadToEnd()

The best solution I have found is:
private void Redirect(StreamReader input, TextBox output)
{
new Thread(a =>
{
var buffer = new char[1];
while (input.Read(buffer, 0, 1) > 0)
{
output.Dispatcher.Invoke(new Action(delegate
{
output.Text += new string(buffer);
}));
};
}).Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
FileName = "php-cgi.exe",
RedirectStandardOutput = true,
UseShellExecute = false,
WorkingDirectory = #"C:\Program Files\Application\php",
}
};
if (process.Start())
{
Redirect(process.StandardOutput, textBox1);
}
}

You can use the OutputDataReceived event to receive data as it's pumped to StdOut.

The problem is due a bad php.ini config. I had the same problem and i downloaded the Windows installer from: http://windows.php.net/download/.
After that and commenting out not needed extensions, the conversion process is alĂ  Speedy Gonzales, converting 20 php per second.
You can safely use "oProcess.StandardOutput.ReadToEnd()". It's more readable and alomost as fast as using the thread solution. To use the thread solution in conjunction with a string you need to introduce an event or something.
Cheers

Related

Determine if a command has been finished executing in CMD in C# [duplicate]

This question already has answers here:
Determining end of console output
(1 answer)
C# StandardOutput hangs, how can I detect it's waiting for input?
(2 answers)
Closed 2 years ago.
Introduction
I am creating a R.A.T (Remote Administration Tool) In c# with TCP client-server configurations.
Everything was going quite fine until I realized a need to detect whether or not a command has been finished executing in a command-prompt process created by my c# application.
Please have a look at the code below.
private static Process CMDProc = null;
private static StreamWriter ToCMDShell = null;
public static void StartCMD()
{
ProcessStartInfo PSInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
CMDProc = new Process { StartInfo = PSInfo };
CMDProc.Start();
ToCMDShell = CMDProc.StandardInput;
ToCMDShell.AutoFlush = true;
CMDProc.BeginOutputReadLine();
CMDProc.BeginErrorReadLine();
CMDProc.OutputDataReceived += (s, e) => { /*Do something with e.Data*/ };
CMDProc.ErrorDataReceived += (s, e) => { /*Do something with e.Data*/ };
ToCMDShell.WriteLineAsync("ping 8.8.8.8"); //Execute a long running command in cmd terminal.
}
What I Want To Achieve
As you may guess that ping command takes variable amount of time to complete depending upon the speed of the internet connection, now what I want is to run a method called CMDCommandExecuted() when a long running command like "ping" finished executing in the terminal which was invoked using the c# code ToCMDShell.WriteLineAsync("any dos command to execute");.
What I Had Tried Till Now
I tried to read the e.Data from the output stream received from the CMDProc.OutputDataReceived Event-Handler but had no luck, because maybe for some other long running commands other than the ping no data at all is being written to the output stream so it is not a bulletproof solution.
And yes I had tried to search for my solutions on the internet as well, yet no luck!
That's why I am here seeking for your help.
It appears that the WriteLineAsync doesn't complete until the long running command does (e.g. the command window is ready for new input), so you just need to Wait on the return from WriteLineAsync when you need to send more data, or know the previous command is done.
private static Process CMDProc = null;
private static StreamWriter ToCMDShell = null;
public static void StartCMD() {
ProcessStartInfo PSInfo = new ProcessStartInfo {
FileName = "cmd.exe",
Arguments = "/k",
CreateNoWindow = false,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
CMDProc = new Process { StartInfo = PSInfo };
CMDProc.Start();
ToCMDShell = CMDProc.StandardInput;
ToCMDShell.AutoFlush = true;
CMDProc.BeginOutputReadLine();
CMDProc.BeginErrorReadLine();
CMDProc.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
CMDProc.ErrorDataReceived += (s, e) => Console.WriteLine($"ERR: {e.Data}");
var run = ToCMDShell.WriteLineAsync("ping 8.8.8.8"); //Execute a long running command in cmd terminal.
// do some stuff
run.Wait(); // wait for long command to complete
ToCMDShell.WriteLine("exit"); //Done
}
Use CMDProc.WaitForExit() to wait for completion and CMDProc.ExitCode to get final status code.
If you get some data from the stream that indicates the process is hung or frozen or needs to be killed, call CMDProc.Kill().
If you get some data from the stream that indicates you should do something else, you can spawn other processes or send additional WriteLine calls to the process to do further processing.
The following program sends the ping command output back to me correctly. Maybe you just need that wait command or a console read line to give it time.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MyNamespace
{
class Program
{
private static Process CMDProc = null;
private static StreamWriter ToCMDShell = null;
public static void Main()
{
StartCMD();
}
public static void StartCMD()
{
ProcessStartInfo PSInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
CMDProc = new Process { StartInfo = PSInfo };
CMDProc.Start();
ToCMDShell = CMDProc.StandardInput;
ToCMDShell.AutoFlush = true;
CMDProc.BeginOutputReadLine();
CMDProc.BeginErrorReadLine();
CMDProc.OutputDataReceived += (s, e) =>
{
Console.WriteLine("PROC: {0}", e.Data);
if (e.Data != null && e.Data.Contains("Average ="))
{
// last line, you don't have to exit here, you could do something else instead...
ToCMDShell.WriteLine("exit");
}
};
CMDProc.ErrorDataReceived += (s, e) => Console.WriteLine("PROC ERR: {0}", e.Data);
ToCMDShell.WriteLine("ping 8.8.8.8"); //Execute a long running command in cmd terminal.
CMDProc.WaitForExit();
Console.WriteLine("Job done, press ENTER to quit");
Console.ReadLine();
}
}
}

Restarting nodejs as a process in C#

I am starting NodeJs as a process inside a C# application. My intent is to restart the process every time it stops.
Code for starting the process is:
_nodeProcess = new Process
{
StartInfo =
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
WorkingDirectory = location,
FileName = "node.exe",
Arguments = "main.js"
}
};
_nodeProcess.EnableRaisingEvents = true;
_nodeProcess.Exited += nodeExited;
_nodeProcess.Start();
string stderrStr = _nodeProcess.StandardError.ReadToEnd();
string stdoutStr = _nodeProcess.StandardOutput.ReadToEnd();
if (!String.IsNullOrWhiteSpace(stderrStr))
{
LogInfoMessage(stderrStr);
}
LogInfoMessage(stdoutStr);
_nodeProcess.WaitForExit();
_nodeProcess.Close();
here is nodeExited method:
private void nodeExited(object sender, EventArgs e)
{
if (!_isNodeStop)
{
this.restartERM_Click(sender, e);
}
else
{
_isNodeStop = false;
}
}
_isNodeStop is just a flag that I set to true when killing a node from the controlled place.
Like this:
private void KillNode()
{
foreach (var process in Process.GetProcessesByName("node"))
{
_isNodeStop = true;
process.Kill();
}
}
My problem is that nodeExited method does not trigger every time node is stopped. I have no clue why and I could not see any pattern. Is just does not stop most of the times.
You are using WaitForExit() anyway, so there is no reason to use the Exited event.
Just manually call your Handler after WaitForExit() like this:
_nodeProcess.WaitForExit();
_nodeProcess.Close();
nodeExited(_nodeProcess, new EventArgs());
and remove
_nodeProcess.EnableRaisingEvents = true;
_nodeProcess.Exited += nodeExited;
Edit:
If I understand this answer correctly, you might also have a deadlock because you call StandardError.ReadToEnd(); and then StandardOutput.ReadToEnd();. The StandardOutput Buffer might be full before it even reaches that point.

How to pass parameter to cmd.exe and get the result back into C# Windows application

GUI of my program.
I want to pass parameter as dir to cmd.exe with .NET Process class and get correct output into the c# program.
Source:
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace MyCmd
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
comboBox1.Items.Add("cmd.exe");
comboBox1.SelectedIndex = 0;
}
private Thread t;
private void button1_Click(object sender, EventArgs e)
{
object[] param = new object[] { comboBox1.Text, txtParams.Text };
if (param.Length > 0 && comboBox1.Text != "")
{
t = new Thread(() => doTask(param));
t.IsBackground = true;
t.Start();
}
else
{
MessageBox.Show("Invalid parameters!");
}
}
private void doTask(object[] param)
{
Process proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = param[0].ToString(),
Arguments = param[1].ToString(),
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
this.Invoke((MethodInvoker)delegate
{
txtResponse.Text += line + Environment.NewLine;
});
}
proc.WaitForExit();
}
}
}
This is output
I want to launch cmd.exe and then enter dir then, it list the directory info into my application.
UPDATED:
Source updated:
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace MyCmdV2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
comboBox1.Items.Add("cmd.exe");
comboBox1.SelectedIndex = 0;
proc = new Process();
proc.StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true
};
proc.Start();
}
private Process proc;
private Thread t;
private void button1_Click(object sender, EventArgs e)
{
t = new Thread(() => doIt());
t.IsBackground = true;
t.Start();
}
private void doIt()
{
if (txtParams.Text.ToLower() == "cls")
{
txtResponse.Text = "";
}
else
{
proc.StandardInput.WriteLine(#txtParams.Text);
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
this.Invoke((MethodInvoker)delegate
{
txtResponse.Text += line + Environment.NewLine;
});
}
proc.WaitForExit();
}
}
}
}
This is an example query only.I need to run any exe program with parameters.
List all my program in to ComboBox and then pass the parameters in TextBox.
Some time i need to pass parameter as IP address like this. \\192.168.1.2
Ex: psinfo.exe \\192.168.1.2
But C# it's store as \\\\192.168.1.2. How to remove that escape sequence characters in parameter.
Your code looks fine as it did pass "dir" as an argument. Your code did the same as executing "cmd.exe dir". Are you wanting to launch cmd.exe and then enter dir so it lists the directory structure? If so, you'll need to redirect the standard input like you did the standard output and then write dir to the input stream.
UPDATE
Code to execute your command:
private void doTask(object[] param)
{
Process proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = param[0].ToString(),
//remove this line, it's not needed
//Arguments = param[1].ToString(),
UseShellExecute = false,
RedirectStandardOutput = true,
//Add this line so you can write commands
RedirectStandardInput = true,
CreateNoWindow = true
}
};
proc.Start();
//now write your command with WriteLine so it mimics an enter press
proc.StandardInput.WriteLine(param[1].ToString());
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
this.Invoke((MethodInvoker)delegate
{
txtResponse.Text += line + Environment.NewLine;
});
}
proc.WaitForExit();
}
Seems that standard input and output are already correctly set. If you take a look in the manual here, you should get a better picture of what to do. I performed two tests using the Windows run feature. The first one was cmd dir, the same as yours and it failed miserably. The second one was cmd /K dir, which produced what I think is the intended output. The used switch leaves the cmd window open, check the manual for other switches that close the process after the command is done.
One more thing to note, while most of the things run in the cmd are programs, commands like "dir" and "cd" are just that, commands, and you won't find dir.exe or cd.exe anywhere.
When you invoke cmd.exe, you need to use the /c parameter. Go to your command prompt and type this:
cmd.exe dir
This is essentially what you do in your application, which gives to the same output as in your application.
Now try:
cmd.exe /c dir
This is the output you are looking for. So, you tell cmd.exe to run the command dir by using the /c param.
So, your ProcessStartInfo.Arguments would look like this:
private void doTask(object[] param)
{
Process proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = param[0].ToString(),
Arguments = "/c " + param[1].ToString(),
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
I have recreated your solution, and this works as expected.

Real Time CMD Output on Windows Defrag.exe

I am trying to capture the output on CMD on REAL TIME. I want to read every line that's being output. The following is my code:
private void Defrag2()
{
string osDrive = Path.GetPathRoot(Environment.SystemDirectory);
Process Psi = new Process();
System.Text.Encoding SysEncoding = System.Text.Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentUICulture.TextInfo.OEMCodePage);
Psi.StartInfo = new ProcessStartInfo("cmd", #"/c defrag " + osDrive + " /a /u")
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = SysEncoding,
StandardErrorEncoding = SysEncoding
};
Psi.EnableRaisingEvents = true;
Psi.OutputDataReceived += new DataReceivedEventHandler(OutPutDataRecieved);
Psi.Start();
Psi.BeginOutputReadLine();
}
void OutPutDataRecieved(object sender, DataReceivedEventArgs e)
{
this.DefStat(e.Data);
}
private void DefStat(string Line)
{
if (Line != null)
{
if (Line.Contains("do not need to def"))
{
defragstatustb.Invoke(new MethodInvoker(() => defragstatustb.Text = "You do not need to defrag this computer."));
}
if (defragRTB.InvokeRequired)
{ defragRTB.Invoke(new MethodInvoker(() => defragRTB.AppendText(Line + Environment.NewLine))); }
}
}
That code works well on Capturing CMD output in real time, EXCEPT when I try to run the Windows Defrag in CMD. For example: If I try to enter a command like "Dir", it reads the output in real time, however if I try to run something like "Defrag C: /f /u", it only reads the output ONLY after it completes the operation.
Any idea how to get this working ? Thank you.
You need to use multi-threading. Create a new thread and and pass it the Outputstream handler and you are good to go. To test if you need multithreading, put this at the start of the DefWork method:
MessageBox.Show(Line);

ProcessInfo and RedirectStandardOutput

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

Categories