Is there a way to get process output the same way It’s written when the process is launched through shell? Basically I need to launch some external processes (A&B). When I launch them through cmd or with UseShellExecute = true the output is printed continuously.
Process A prints its progress line by line. Process B displays and updates a text-based progress bar.
For process A, OutputDataReceived does not fire after each output line that is normally printed if the process is launched through shell. It fires after each 20 lines approximately (it fires 20 times so I have the whole output, but I can’t capture the output in real time). I guess that it fires after stdOut is flushed, but then how cmd does it to print partial output and what can I do to read it?
For process B, OutputDataReceived fires only once the process is done.
My code:
public static bool ExecuteProcess(string Path, string CommandLine, Action<string> OutputLineDelegate)
{
using (Process ChildProcess = new Process())
{
ChildProcess.StartInfo.FileName = Path;
ChildProcess.StartInfo.Arguments = CommandLine;
ChildProcess.StartInfo.UseShellExecute = false;
ChildProcess.StartInfo.CreateNoWindow = true;
ChildProcess.StartInfo.RedirectStandardOutput = true;
ChildProcess.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e)
{
OutputLineDelegate(e.Data);
};
ChildProcess.Start();
ChildProcess.WaitForExit();
var ExitCode = ChildProcess.ExitCode;
OutputLineDelegate(string.Format("Exit code {0}", ExitCode));
return ExitCode == 0 ? true : false;
}
}
I’ve tried to reading stdOut char by char, but both StreamReader.Read() and StreamReader.Peek() wait until anything is in the output.
Related
This question already has answers here:
C# StandardOutput hangs, how can I detect it's waiting for input?
(2 answers)
Is my process waiting for input?
(3 answers)
Closed 1 year ago.
I'm essentially building a remote command line using a Callback WCF Service (with the ultimate goal to manage & update game server instances on my personal server mostly by way of SteamCMD). I am able to send commands to the remote service, have them run in "cmd.exe", and successfully receive real-time progress updates to a WinForm and MVC website test client
However, I would like to add functionality to wait for/provide user input if necessary, mostly because some Steam games servers do not allow anonymous login through SteamCMD and the user will need to enter their steam password or 2FA. A good example I am using for testing input is the "date" command in windows command prompt. Normally, it prints the date then prompts the user to "Enter a new date".
But, when I run the "Date" command through my service, the prompt to enter the new date does not get read on the ReadLine(), and instead just indefinitely waits for input I assume.
The problems I am running into:
How do I detect when the Process is waiting for user input
The Process is not outputting the last line that typically prompts the user for input (hangs on ReadLine )
The following code blocks are the main functions handling the Process and the redirect of StdOut. "RunCommandLine" pretty much starts the Process and sets all the event handlers, and the "Processer_DoWork" handles pushing the StdOut to the CallbackFunction so the client can see the output in real time.
Is what I am seeking possible with how I have this set up? I was able to implement an interrupt functionality, so I'm thinking I can do something similar for passing the actual input to the process.. I am just lost on how to detect the input is actually needed.
Thanks in advance for any help!
private BackgroundWorker Processer = new BackgroundWorker();
public IGameManagerCallback Callback;
public void RunCommandLine(string WorkingPath, string Commands)
{
if (Processer.IsBusy)
{
return;
}
Processer.WorkerReportsProgress = true;
Processer.WorkerSupportsCancellation = true;
Processer.ProgressChanged += Processer_ProgressChanged;
Processer.DoWork += Processer_DoWork;
Processer.RunWorkerCompleted += Processer_WorkComplete;
Callback = OperationContext.Current.GetCallbackChannel<IGameManagerCallback>();
ProcessStartInfo StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $#"/c {Commands}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
CreateNoWindow = true
};
if (!string.IsNullOrEmpty(WorkingPath))
StartInfo.WorkingDirectory = Path.GetDirectoryName($#"{WorkingPath}");
Process CommandProcess;
try { CommandProcess = Process.Start(StartInfo); }
catch (Exception ex)
{
Console.WriteLine($"Error starting: {ex.Message}");
return;
}
//attach to background processor
Processer.RunWorkerAsync(CommandProcess);
}
private void Processer_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
Process proc = e.Argument as Process;
StreamReader StandardOutput = proc.StandardOutput;
StreamWriter StandardInput = proc.StandardInput;
string data = StandardOutput.ReadLine();
while (data != null)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
Processer.ReportProgress(0, data);
data = StandardOutput.ReadLine();//this is where we hang when there is input required
}
}
I am currently working on a C# Program which needs to call a local PHP script and write its output to a file. The problem is, that I need to be able to stop the execution of the script.
First, I tried to call cmd.exe and let cmd write the output to the file which worked fine. But I found out, that killing the cmd process does not stop the php cli.
So I tried to call php directly, redirect its output and write it from the C# code to a file. But here the problem seems to be, that the php cli does not terminate when the script is done. process.WaitForExit() does not return, even when I am sure that the script has been fully executed.
I cannot set a timeout to the WaitForExit(), because depending on the arguments, the script may take 3 minutes or eg. 10 hours.
I do not want to kill just a random php cli, there may be others currently running.
What is the best way to call a local php script from C#, writing its output to a file and beeing able to stop the execution?
Here is my current code:
// Create the process
var process = new System.Diagnostics.Process();
process.EnableRaisingEvents = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "php.exe";
// CreateExportScriptArgument returns something like "file.php arg1 arg2 ..."
process.StartInfo.Arguments = CreateExportScriptArgument(code, this.content, this.options);
process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
process.StartInfo.RedirectStandardOutput = true;
// Start the process or cancel, if the process should not run
if (!this.isRunning) { return; }
this.currentProcess = process;
process.Start();
// Get the output
var output = process.StandardOutput;
// Wait for the process to finish
process.WaitForExit();
this.currentProcess = null;
To kill the process I am using:
// Mark as not running to prevent starting new
this.isRunning = false;
// Kill the process
if (this.currentProcess != null)
{
this.currentProcess.Kill();
}
Thanks for reading!
EDIT
That the cli does not return seems to be not reproducible. When I test a different script (without arguments) it works, probably its the script or the passing of the arguments.
Running my script from cmd works just fine, so the script should not be the problem
EDIT 2
When disabling RedirectStandardOutput, the cli quits. could it be, that I need to read the output, before the process finishes? Or does the process wait, when some kind of buffer is full?
EDIT 3: Problem solved
Thanks to VolkerK, I / we found a solution. The problem was, that WaitForExit() did not get called, when the output is not read (probably due to a full buffer in the standard output). My script wrote much output.
What works for me:
process.Start();
// Get the output
var output = process.StandardOutput;
// Read the input and write to file, live to avoid reading / writing to much at once
using (var file = new StreamWriter("path\\file", false, new UTF8Encoding()))
{
// Read each line
while (!process.HasExited)
{
file.WriteLine(output.ReadLine());
}
// Read the rest
file.Write(output.ReadToEnd());
// flush to file
file.Flush();
}
Since the problem was that the output buffer was full and therefore the php process stalled while waiting to send its output, asynchronously reading the output in the c# program is the solution.
class Program {
protected static /* yeah, yeah, it's only an example */ StringBuilder output;
static void Main(string[] args)
{
// Create the process
var process = new System.Diagnostics.Process();
process.EnableRaisingEvents = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "php.exe";
process.StartInfo.Arguments = "-f path\\test.php mu b 0 0 pgsql://user:pass#x.x.x.x:5432/nominatim";
process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
process.StartInfo.RedirectStandardOutput = true;
output = new StringBuilder();
process.OutputDataReceived += process_OutputDataReceived;
// Start the process
process.Start();
process.BeginOutputReadLine();
// Wait for the process to finish
process.WaitForExit();
Console.WriteLine("test");
// <-- do something with Program.output here -->
Console.ReadKey();
}
static void process_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (!String.IsNullOrEmpty(e.Data)) {
// edit: oops the new-line/carriage-return characters are not "in" e.Data.....
// this _might_ be a problem depending on the actual output.
output.Append(e.Data);
output.Append(Environment.NewLine);
}
}
}
see also: https://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline%28v=vs.110%29.aspx
I'm trying To run Plink via C#
using this code
public void RunProcess(string FileName, string Arguments, bool EventWhenExit , bool IsWaitBeforeStart = true)
{
process = new Process();
process.OutputDataReceived += new DataReceivedEventHandler(OnDataReceivedEvent);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = FileName; // Gets or sets the application or document to start.
process.StartInfo.Arguments = Arguments;//Gets or sets the set of command-line arguments to use when starting the application
if (IsWaitBeforeStart) Thread.Sleep(5000);
if (EventWhenExit)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(myprocess_Exited);
}
process.Start();
process.BeginOutputReadLine();
PID = process.Id;
ProcessInputStream = process.StandardInput;
}
When I run Plink (using this arg : -telnet #IPAddr)
I notice that last row only fires when I close the process .
My guess is that it keeps the last row until last row signal or something like that fired
How can make the process (not only Plink of-course ) to fire every thing it has in it's buffer and not when special signal like fired (like process exit or new line )
BeginOutputReadLine() waits for a newline character or the end of the stream. Unfortunately there is no BeginOutputRead() method that provides the behavior you desire. You do have access to process.StandardOutput though (a StreamReader), on which Read() operations do return whenever data is available.
Currently a line in a console application is "finished" as soon as it is terminated by a newline character. Since for this last line no newline character was outputted yet, you'd have to somehow determine whether it has finished or not. Consider the following example:
Console.Write("This is the ");
Console.Write(" plink output ");
Console.Write(" that I'm trying to read");
LongRunningActivity();
Console.Write(".");
Console.WriteLine();
You could receive these segments of data separately. When is the line finished? Before LongRunningActivity()?
So when attempting to read data before a newline character, you'll have to think of some rules to determine whether the message has completed.
Example of performing this task in a separate thread:
...
process.Start();
Task.Factory.StartNew(new Action<object>(ReadFromStreamReader), process.StandardOutput);
void ReadFromStreamReader(object state)
{
StreamReader reader = state as StreamReader;
char[] buffer = new char[1024];
int chars;
while ((chars = reader.Read(buffer, 0, buffer.Length)) > 0)
{
string data = new string(buffer, 0, chars);
OnDataReceived(data);
}
// You arrive here when process is terminated.
}
void OnDataReceived(string data)
{
// Process the data here. It might contain only a chunk of a full line
// remember to use Invoke() if you want to update something in your form GUI
}
I have a console based c app .
I am executing it from c# silently using Redirecting Standard Output and doing it synchronously which works fine.
Now i want to do it in asynch manner which is giving output like synch manner.
i.e
OutPutDataRecieved event is fired but only after the console app(exe) finishes.OutputDataRecieved event is fired for each line after finish, not instantly as soon as it gets a line in output.
The code for asynch works for CMD.exe etc ,So,I am sure its c based app having problem in output.
FYI:The output in c console is done using printf.
Based on my findings:
I think c console app is not giving output/writing to stdout until it finishes its execution.
I tried setting buffer to null or flushing after every printf but none works.
Any tricks??
Thanks man.That worked like a charm.
I was using setbuf to set buffer null.
Really appreciate efforts of all you guyz.
FOr info of other guyz,this was my c# code which is available on internet forums and SO too.
string command = #"Output.exe";
string arguments = "hellotext";
ProcessStartInfo info = new ProcessStartInfo(command, arguments);
// Redirect the standard output of the process.
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
// Set UseShellExecute to false for redirection
info.UseShellExecute = false;
Process proc = new Process();
proc.StartInfo = info;
proc.EnableRaisingEvents = true;
// Set our event handler to asynchronously read the sort output.
proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
proc.ErrorDataReceived += new DataReceivedEventHandler(proc_ErrorDataReceived);
proc.Exited += new EventHandler(proc_Exited);
proc.Start();
// Start the asynchronous read of the sort output stream. Note this line!
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.WaitForExit();
Console.WriteLine("Exited (Main)");
}
static void proc_Exited(object sender, EventArgs e)
{
Console.WriteLine("Exited (Event)");
}
static void proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Error: {0}", e.Data);
}
static void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine("Output data: {0}", e.Data);
}
You can disable the buffering using setvbuf.
Here is a quick example, if you remove the call to setvbuf then the redirected content is only written once you press enter (waiting on the getchar()). With the setvbuf, the string is written to the redirected stream directly.
int _tmain(int argc, _TCHAR* argv[])
{
setvbuf(stdout, NULL,_IONBF, 0);
printf("Hello");
getchar();
return 0;
}
I have a Process:
Process pr = new Process();
pr.StartInfo.FileName = #"wput.exe";
pr.StartInfo.Arguments = #"C:\Downloads\ ftp://user:dvm#172.29.200.158/Transfer/Updates/";
pr.StartInfo.RedirectStandardOutput = true;
pr.StartInfo.UseShellExecute = false;
pr.StartInfo.
pr.Start();
string output = pr.StandardOutput.ReadToEnd();
Console.WriteLine("Output:");
Console.WriteLine(output);
Wput is an ftp upload client.
At the moment when I run the process and begin the upload, the app freezes and the console output won't show until the end. I guess the first problem is solvable by using a Thread.
What I want to do is start an upload, have it pause every so often, read whatever output has been generated (use this data do make progress bar etc), and begin again.
What classes/methods should I be looking into?
You can use the OutputDataReceived event to print the output asynchronously. There are a few requirements for this to work:
The event is enabled during asynchronous read operations on StandardOutput. To start asynchronous read operations, you must redirect the StandardOutput stream of a Process, add your event handler to the OutputDataReceived event, and call BeginOutputReadLine. Thereafter, the OutputDataReceived event signals each time the process writes a line to the redirected StandardOutput stream, until the process exits or calls CancelOutputRead.
An example of this working is below. It's just doing a long running operation that also has some output (findstr /lipsn foo * on C:\ -- look for "foo" in any file on the C drive). The Start and BeginOutputReadLine calls are non-blocking, so you can do other things while the console output from your FTP application rolls in.
If you ever want to stop reading from the console, use the CancelOutputRead/CancelErrorRead methods. Also, in the example below, I'm handling both standard output and error output with a single event handler, but you can separate them and deal with them differently if needed.
using System;
using System.Diagnostics;
namespace AsyncConsoleRead
{
class Program
{
static void Main(string[] args)
{
Process p = new Process();
p.StartInfo.FileName = "findstr.exe";
p.StartInfo.Arguments = "/lipsn foo *";
p.StartInfo.WorkingDirectory = "C:\\";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.OutputDataReceived += new DataReceivedEventHandler(OnDataReceived);
p.ErrorDataReceived += new DataReceivedEventHandler(OnDataReceived);
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
}
static void OnDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
}
}
The best method would be to use libraries which support FTP, instead of relying on external applications. If you don't need much info from the external application and are not verifying their outputs, then go ahead. Else better use FTP client libs.
May be you would like to see libs/documentations:
http://msdn.microsoft.com/en-us/library/ms229711.aspx
http://www.codeproject.com/KB/IP/ftplib.aspx
http://www.c-sharpcorner.com/uploadfile/danglass/ftpclient12062005053849am/ftpclient.aspx