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.
Related
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();
}
}
}
In a Windows Forms project I have a handler for a button that opens a file in Notepad for editing. Once notepad closes I call a function RefreshTextBox() to parse the text file and update a TextBox based on a value. Here is the method that opens Notepad and calls the refresh method once its closed:
private void button_Click(object sender, EventArgs e)
{
Process p = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = "NOTEPAD.EXE",
Arguments = _path,
WindowStyle = ProcessWindowStyle.Maximized,
CreateNoWindow = false
}
};
p.Exited += (a, b) =>
{
RefreshTextBox();
p.Dispose();
};
p.Start();
}
And code to refresh the textbox:
private void RefreshTextBox()
{
using (StreamReader reader = File.OpenText(_appSettingsPath))
{
string text = reader.ReadToEnd();
// Code to parse text looking for value...
// InvalidOperationException thrown here:
textBox.Text = reader.Value.ToString();
}
}
This throws an Exception for trying to update the Control from a thread other than the one it was created on. I'm having trouble understanding why though. I'm not doing this in a new task or backgroundworker or anything like that. Obviously notepad is running in another thread, but the refresh method isn't called until after it's process has exited.
Edit: I should add that this error throws up a Fatal Exception popup when debugging in Visual Studio (as an Admin). It doesn't show the popup when running the application on its own, either the exception is silently swallowed or it doesn't occur then.
As per documentation if Process SynchronizingObject is not set it will execute exited event in system threadpool to avoid this and run that event handler in UI thread you need to set SynchronizingObject to Form Instance
When SynchronizingObject is null, methods that handle the Exited event are called on a thread from the system thread pool. For more information about system thread pools, see ThreadPool.
If you set
p.SynchronizingObject = WindowsFormName;
Then it will run in same thread or it will execute in a system threadpool thread which will cause crossthread exception.
MSDN Reference
private void button_Click(object sender, EventArgs e)
{
Process p = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = "NOTEPAD.EXE",
Arguments = _path,
WindowStyle = ProcessWindowStyle.Maximized,
CreateNoWindow = false
}
};
//p.SynchronizingObject = this;
p.Exited += (a, b) =>
{
RefreshTextBox();
p.Dispose();
};
p.Start();
}
private void RefreshTextBox()
{
using (StreamReader reader = File.OpenText(_appSettingsPath))
{
string text = reader.ReadToEnd();
// Code to parse text looking for value...
//textBox.Text = text; // reader.Value.ToString();
threadSafeControlUpdate(textBox, text);
}
}
public delegate void updateUIfunc(Control c, object v);
public void threadSafeControlUpdate(Control c, object v)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new updateUIfunc(threadSafeControlUpdate), c, v);
return;
}
if (c is TextBox && v is string)
{
c.Text = (string)v;
}
}
I would recommend capturing the synchronization context and posting the RefreshTextBox call onto it. Something like:
private void button_Click(object sender, EventArgs e)
{
var _synchronizationContext = WindowsFormsSynchronizationContext.Current;
Process p = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = "NOTEPAD.EXE",
Arguments = _path,
WindowStyle = ProcessWindowStyle.Maximized,
CreateNoWindow = false
}
};
p.Exited += (a, b) =>
{
_synchronizationContext.Post(_=> RefreshTextBox(), null);
p.Dispose();
};
p.Start();
}
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);
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
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