I'm running a program using command line in c# this program produce some logs while its running in need to display this logs whenever it get change. I wrote the following code but it shows all the logs once the process has been killed and during the running time my program is not responding. how can I fix it?
regards
ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + "C:\\server.py");
Process proc = new Process();
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
procStartInfo.UseShellExecute = false;
procStartInfo.RedirectStandardOutput = true;
//procStartInfo.CreateNoWindow = true;
proc.StartInfo = procStartInfo;
proc.Start();
string output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit(300);
LogstextBox.Text = output;
Edited:
well, I tried to use OutputDataReceived but it doesn't show any result, here is the changed code:
{
//processCaller.FileName = #"ping";
//processCaller.Arguments = "4.2.2.4 -t"; this is working
processCaller.FileName = #"cmd.exe";
processCaller.Arguments = "/c c:\\server.py"; //this is not working
processCaller.StdErrReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.StdOutReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.Completed += new EventHandler(processCompletedOrCanceled);
processCaller.Cancelled += new EventHandler(processCompletedOrCanceled);
this.richTextBox1.Text = "Server Started.." + Environment.NewLine;
processCaller.Start();
}
private void writeStreamInfo(object sender, DataReceivedEventArgs e)
{
this.richTextBox1.AppendText(e.Text + Environment.NewLine);
}
This is the problem:
string output = proc.StandardOutput.ReadToEnd();
You won't get to the "end" of standard output until the process has terminated.
You should be reading a line at a time - or possibly just subscribing to the OutputDataReceived event (and following the documented other requirements for that event).
EDIT: Here's sample code which works for me:
using System;
using System.Diagnostics;
using System.Threading;
class Program
{
public static void Main()
{
ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/c " + "type Test.cs")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process process = Process.Start(startInfo);
process.OutputDataReceived += (sender, e) => Console.WriteLine(e.Data);
process.BeginOutputReadLine();
process.WaitForExit();
// We may not have received all the events yet!
Thread.Sleep(5000);
}
}
Note that in your sample code, you're accessing the UI on whatever thread the OutputDataReceived handler is called - that looks like a bad idea to me.
You can use the Process.BeginOutputReadLine Method. The link shows a complete working example in C# which uses the OutputDataReceived event. That code example should do what you want.
Related
I would like to run an external command line program from my Mono/.NET app.
For example, I would like to run mencoder. Is it possible:
To get the command line shell output, and write it on my text box?
To get the numerical value to show a progress bar with time elapsed?
When you create your Process object set StartInfo appropriately:
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "program.exe",
Arguments = "command line arguments to your executable",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
then start the process and read from it:
proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
// do something with line
}
You can use int.Parse() or int.TryParse() to convert the strings to numeric values. You may have to do some string manipulation first if there are invalid numeric characters in the strings you read.
You can process your output synchronously or asynchronously.
1. Synchronous example
static void runCommand()
{
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c DIR"; // Note the /c command (*)
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.Start();
//* Read the output (or the error)
string output = process.StandardOutput.ReadToEnd();
Console.WriteLine(output);
string err = process.StandardError.ReadToEnd();
Console.WriteLine(err);
process.WaitForExit();
}
Note that it's better to process both output and errors: they must be handled separately.
(*) For some commands (here StartInfo.Arguments) you must add the /c directive, otherwise the process freezes in the WaitForExit().
2. Asynchronous example
static void runCommand()
{
//* Create your Process
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c DIR";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
//* Start process and handlers
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
//* Do your stuff with the output (write to console/log/StringBuilder)
Console.WriteLine(outLine.Data);
}
If you don't need to do complicate operations with the output, you can bypass the OutputHandler method, just adding the handlers directly inline:
//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
Alright, for anyone who wants both Errors and Outputs read, but gets deadlocks with any of the solutions, provided in other answers (like me), here is a solution that I built after reading MSDN explanation for StandardOutput property.
Answer is based on T30's code:
static void runCommand()
{
//* Create your Process
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c DIR";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
//* Set ONLY ONE handler here.
process.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
//* Start process
process.Start();
//* Read one element asynchronously
process.BeginErrorReadLine();
//* Read the other one synchronously
string output = process.StandardOutput.ReadToEnd();
Console.WriteLine(output);
process.WaitForExit();
}
static void ErrorOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
//* Do your stuff with the output (write to console/log/StringBuilder)
Console.WriteLine(outLine.Data);
}
The standard .NET way of doing this is to read from the Process' StandardOutput stream. There is an example in the linked MSDN docs. Similar, you can read from StandardError, and write to StandardInput.
It is possible to get the command line shell output of a process as described here : http://www.c-sharpcorner.com/UploadFile/edwinlima/SystemDiagnosticProcess12052005035444AM/SystemDiagnosticProcess.aspx
This depends on mencoder. If it ouputs this status on the command line then yes :)
you can use shared memory for the 2 processes to communicate through, check out MemoryMappedFile
you'll mainly create a memory mapped file mmf in the parent process using "using" statement then create the second process till it terminates and let it write the result to the mmf using BinaryWriter, then read the result from the mmf using the parent process, you can also pass the mmf name using command line arguments or hard code it.
make sure when using the mapped file in the parent process that you make the child process write the result to the mapped file before the mapped file is released in the parent process
Example:
parent process
private static void Main(string[] args)
{
using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("memfile", 128))
{
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(512);
}
Console.WriteLine("Starting the child process");
// Command line args are separated by a space
Process p = Process.Start("ChildProcess.exe", "memfile");
Console.WriteLine("Waiting child to die");
p.WaitForExit();
Console.WriteLine("Child died");
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryReader reader = new BinaryReader(stream);
Console.WriteLine("Result:" + reader.ReadInt32());
}
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
Child process
private static void Main(string[] args)
{
Console.WriteLine("Child process started");
string mmfName = args[0];
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(mmfName))
{
int readValue;
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryReader reader = new BinaryReader(stream);
Console.WriteLine("child reading: " + (readValue = reader.ReadInt32()));
}
using (MemoryMappedViewStream input = mmf.CreateViewStream())
{
BinaryWriter writer = new BinaryWriter(input);
writer.Write(readValue * 2);
}
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
to use this sample, you'll need to create a solution with 2 projects inside, then you take the build result of the child process from %childDir%/bin/debug and copy it to %parentDirectory%/bin/debug then run the parent project
childDir and parentDirectory are the folder names of your projects on the pc
good luck :)
You can log process output using below code:
ProcessStartInfo pinfo = new ProcessStartInfo(item);
pinfo.CreateNoWindow = false;
pinfo.UseShellExecute = true;
pinfo.RedirectStandardOutput = true;
pinfo.RedirectStandardInput = true;
pinfo.RedirectStandardError = true;
pinfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
var p = Process.Start(pinfo);
p.WaitForExit();
Process process = Process.Start(new ProcessStartInfo((item + '>' + item + ".txt"))
{
UseShellExecute = false,
RedirectStandardOutput = true
});
process.WaitForExit();
string output = process.StandardOutput.ReadToEnd();
if (process.ExitCode != 0) {
}
How to launch a process (such as a bat file, perl script, console program) and have its standard output displayed on a windows form:
processCaller = new ProcessCaller(this);
//processCaller.FileName = #"..\..\hello.bat";
processCaller.FileName = #"commandline.exe";
processCaller.Arguments = "";
processCaller.StdErrReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.StdOutReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.Completed += new EventHandler(processCompletedOrCanceled);
processCaller.Cancelled += new EventHandler(processCompletedOrCanceled);
// processCaller.Failed += no event handler for this one, yet.
this.richTextBox1.Text = "Started function. Please stand by.." + Environment.NewLine;
// the following function starts a process and returns immediately,
// thus allowing the form to stay responsive.
processCaller.Start();
You can find ProcessCaller on this link: Launching a process and displaying its standard output
I was running into the infamous deadlock problem when calling Process.StandardOutput.ReadLine and Process.StandardOutput.ReadToEnd.
My goal/use case is simple. Start a process and redirect it's output so I can capture that output and log it to the console via .NET Core's ILogger<T> and also append the redirected output to a file log.
Here's my solution using the built in async event handlers Process.OutputDataReceived and Process.ErrorDataReceived.
var p = new Process
{
StartInfo = new ProcessStartInfo(
command.FileName, command.Arguments
)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
}
};
// Asynchronously pushes StdOut and StdErr lines to a thread safe FIFO queue
var logQueue = new ConcurrentQueue<string>();
p.OutputDataReceived += (sender, args) => logQueue.Enqueue(args.Data);
p.ErrorDataReceived += (sender, args) => logQueue.Enqueue(args.Data);
// Start the process and begin streaming StdOut/StdErr
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
// Loop until the process has exited or the CancellationToken is triggered
do
{
var lines = new List<string>();
while (logQueue.TryDequeue(out var log))
{
lines.Add(log);
_logger.LogInformation(log)
}
File.AppendAllLines(_logFilePath, lines);
// Asynchronously sleep for some time
try
{
Task.Delay(5000, stoppingToken).Wait(stoppingToken);
}
catch(OperationCanceledException) {}
} while (!p.HasExited && !stoppingToken.IsCancellationRequested);
The solution that worked for me in win and linux is the folling
// GET api/values
[HttpGet("cifrado/{xml}")]
public ActionResult<IEnumerable<string>> Cifrado(String xml)
{
String nombreXML = DateTime.Now.ToString("ddMMyyyyhhmmss").ToString();
String archivo = "/app/files/"+nombreXML + ".XML";
String comando = " --armor --recipient bibankingprd#bi.com.gt --encrypt " + archivo;
try{
System.IO.File.WriteAllText(archivo, xml);
//String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient licorera#local.com --armor --encrypt C:\\Users\\Administrador\\Documents\\pruebas\\nuevo.xml ";
ProcessStartInfo startInfo = new ProcessStartInfo() {FileName = "/usr/bin/gpg", Arguments = comando };
Process proc = new Process() { StartInfo = startInfo, };
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.Start();
proc.WaitForExit();
Console.WriteLine(proc.StandardOutput.ReadToEnd());
return new string[] { "Archivo encriptado", archivo + " - "+ comando};
}catch (Exception exception){
return new string[] { archivo, "exception: "+exception.ToString() + " - "+ comando };
}
}
System.Diagnostics.Process is not the most pleasant to work with, so you may want to try CliWrap. It offers many different models for working with output, including piping, buffering, and real-time streaming. Here are some examples (taken from readme).
Simply launch a command line executable:
using CliWrap;
var result = await Cli.Wrap("path/to/exe")
.WithArguments("--foo bar")
.WithWorkingDirectory("work/dir/path")
.ExecuteAsync();
// Result contains:
// -- result.ExitCode (int)
// -- result.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
Launch a command line executable and buffer stdout/stderr in-memory:
using CliWrap;
using CliWrap.Buffered;
// Calling `ExecuteBufferedAsync()` instead of `ExecuteAsync()`
// implicitly configures pipes that write to in-memory buffers.
var result = await Cli.Wrap("path/to/exe")
.WithArguments("--foo bar")
.WithWorkingDirectory("work/dir/path")
.ExecuteBufferedAsync();
// Result contains:
// -- result.StandardOutput (string)
// -- result.StandardError (string)
// -- result.ExitCode (int)
// -- result.StartTime (DateTimeOffset)
// -- result.ExitTime (DateTimeOffset)
// -- result.RunTime (TimeSpan)
Launch a command line executable with manual pipe configuration:
using CliWrap
var buffer = new StringBuilder();
var result = await Cli.Wrap("foo")
.WithStandardOutputPipe(PipeTarget.ToFile("output.txt"))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(buffer))
.ExecuteAsync();
Launch a command line executable as an event stream:
using CliWrap;
using CliWrap.EventStream;
var cmd = Cli.Wrap("foo").WithArguments("bar");
await foreach (var cmdEvent in cmd.ListenAsync())
{
switch (cmdEvent)
{
case StartedCommandEvent started:
_output.WriteLine($"Process started; ID: {started.ProcessId}");
break;
case StandardOutputCommandEvent stdOut:
_output.WriteLine($"Out> {stdOut.Text}");
break;
case StandardErrorCommandEvent stdErr:
_output.WriteLine($"Err> {stdErr.Text}");
break;
case ExitedCommandEvent exited:
_output.WriteLine($"Process exited; Code: {exited.ExitCode}");
break;
}
}
I've got a tricky issue with a console app, from which I'm trying to redirect StandardInput, StandardOutput and StandardError.
I've got a working solution for other console app - that's not a new thing for me. But this app seems to have something special, which is making impossible to redirect all outputs with a standard approach.
Console app works like this:
directly after startup writes a few lines and waits for user input
no matter what input is - console app is showing some output and again wait for new input
console app never ends, it has to be closed
I've tried already solutions based on:
StandardOutput/Error.ReadToEnd()
taki care of OutputDataReceived & ErrorDataReceived with read line by line with ReadLine
reading by char
waiting for the end of process (which is not ending, so I'm running into a deadlock)
to start console app in a preloaded cmd.exe and grab this (StandardOutput stopped to show just after launch of this console app)
to manually flush input
All the time I had completely no output and no error stream from console app - nothing.
After a multitude attempts I've discovered, that I can receive StandardOutput only when I'll close StandardInput after programatically inputting the data.
But in this case, console app is going wild, falling into loop of writing few lines to StandardOutput as on start-up, which makes final output big and full of garbages.
With MedallionShell library I'm able to try to gracefully kill it with Ctrl+C, but this solution is still far from acceptable, because:
sometimes console app will produce so much garbages before I will be able to kill it, that it crashes my app
even if this won't crash, searching for expected output in a lot of garbages is nasty and tragically slows down automatization (6000 records in... 15 minutes)
I'm unable to provide more than one input at a time, so I have to start console app just to receive one output, close and start again for another output
I've got no sources for that console app, so I'm even not able to recreate the issue or fix it - it's some very legacy app at my company, which I'm trying to make at least a bit automatic...
Code, with which I've got at least anything now (with MediallionShell):
var command = Command.Run(Environment.CurrentDirectory + #"\console_app.exe");
command.StandardInput.WriteLine("expected_input");
command.StandardInput.Close(); // without that I'll never receive any output or error stream from this stubborn console app
command.TrySignalAsync(CommandSignal.ControlC); // attempt to kill gracefully, not always successfull...
var result = command.Result;
textBox1.AppendText(String.Join(Environment.NewLine, command.GetOutputAndErrorLines().ToArray().Take(10))); // trying to get rid of garbages
command.Kill(); // additional kill attempt if Ctrl+C didn't help, sometimes even this fails
Any help will be appreciated, I'm also still searching for solution and now I'm checking this one: PostMessage not working with console window whose output is redirected and read asynchronously but author there had an output and I don't...
You haven't provided a sample Console program to test with, but something like the following may work:
Create a Console project (Console (.NET Framework)) - used for testing
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleTestApp
{
class Program
{
static void Main(string[] args)
{
//prompt for input - 1st prompt
Console.Write("Please enter filename: ");
string filename = Console.ReadLine();
if (System.IO.File.Exists(filename))
{
Console.WriteLine("'" + filename + "' exists.");
}
else
{
Console.Error.WriteLine("'" + filename + "' doesn't exist.");
}
//prompt for input - 2nd prompt
Console.Write("Would you like to exit? ");
string answer = Console.ReadLine();
Console.WriteLine("Your answer was: " + answer);
Console.WriteLine("Operation complete.");
}
}
}
Then, create a Windows Forms project Windows Forms (.NET Framework) and run one of the following:
Option 1:
private void RunCmd(string exePath, string arguments = null)
{
//create new instance
ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
startInfo.Arguments = arguments; //arguments
startInfo.CreateNoWindow = true; //don't create a window
startInfo.RedirectStandardError = true; //redirect standard error
startInfo.RedirectStandardOutput = true; //redirect standard output
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.ErrorDialog = false;
//create new instance
using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
{
//subscribe to event and add event handler code
p.ErrorDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
//ToDo: add desired code
Debug.WriteLine("Error: " + e.Data);
}
};
//subscribe to event and add event handler code
p.OutputDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
//ToDo: add desired code
Debug.WriteLine("Output: " + e.Data);
}
};
p.Start(); //start
p.BeginErrorReadLine(); //begin async reading for standard error
p.BeginOutputReadLine(); //begin async reading for standard output
using (StreamWriter sw = p.StandardInput)
{
//provide values for each input prompt
//ToDo: add values for each input prompt - changing the for loop as necessary
for (int i = 0; i < 2; i++)
{
if (i == 0)
sw.WriteLine(#"C:\Temp\Test1.txt"); //1st prompt
else if (i == 1)
sw.WriteLine("Yes"); //2nd prompt
else
break; //exit
}
}
//waits until the process is finished before continuing
p.WaitForExit();
}
}
Option 2:
private void RunCmd(string exePath, string arguments = null)
{
//create new instance
ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
startInfo.Arguments = arguments; //arguments
startInfo.CreateNoWindow = true; //don't create a window
startInfo.RedirectStandardError = true; //redirect standard error
startInfo.RedirectStandardOutput = true; //redirect standard output
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.ErrorDialog = false;
//create new instance
using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
{
//subscribe to event and add event handler code
p.ErrorDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
//ToDo: add desired code
Debug.WriteLine("Error: " + e.Data);
}
};
//subscribe to event and add event handler code
p.OutputDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
//ToDo: add desired code
Debug.WriteLine("Output: " + e.Data);
}
};
p.Start(); //start
p.BeginErrorReadLine(); //begin async reading for standard error
p.BeginOutputReadLine(); //begin async reading for standard output
using (StreamWriter sw = p.StandardInput)
{
//provide values for each input prompt
//ToDo: add values for each input prompt - changing the for loop as necessary
sw.WriteLine(#"C:\Temp\Test1.txt"); //1st prompt
sw.WriteLine("Yes"); //2nd prompt
}
//waits until the process is finished before continuing
p.WaitForExit();
}
}
Option 3:
Note: This one is modified from here.
private void RunCmd(string exePath, string arguments = null)
{
//create new instance
ProcessStartInfo startInfo = new ProcessStartInfo(exePath, arguments);
startInfo.Arguments = arguments; //arguments
startInfo.CreateNoWindow = true; //don't create a window
startInfo.RedirectStandardError = true; //redirect standard error
startInfo.RedirectStandardOutput = true; //redirect standard output
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false; //if true, uses 'ShellExecute'; if false, uses 'CreateProcess'
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.ErrorDialog = false;
//create new instance
using (Process p = new Process { StartInfo = startInfo, EnableRaisingEvents = true })
{
p.Start(); //start
Read(p.StandardOutput);
Read(p.StandardError);
using (StreamWriter sw = p.StandardInput)
{
//provide values for each input prompt
//ToDo: add values for each input prompt - changing the for loop as necessary
sw.WriteLine(#"C:\Temp\Test1.txt"); //1st prompt
sw.WriteLine("Yes"); //2nd prompt
}
//waits until the process is finished before continuing
p.WaitForExit();
}
}
private static void Read(StreamReader reader)
{
new System.Threading.Thread(() =>
{
while (true)
{
int current;
while ((current = reader.Read()) >= 0)
Console.Write((char)current);
}
}).Start();
}
This question already has answers here:
Capturing console output from a .NET application (C#)
(8 answers)
Closed 6 years ago.
I need to spawn a child process that is a console application, and capture its output.
I wrote up the following code for a method:
string retMessage = String.Empty;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;
startInfo.Arguments = command;
startInfo.FileName = exec;
p.StartInfo = startInfo;
p.Start();
p.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object sender, DataReceivedEventArgs e)
{
using (StreamReader output = p.StandardOutput)
{
retMessage = output.ReadToEnd();
}
}
);
p.WaitForExit();
return retMessage;
However, this does not return anything. I don't believe the OutputDataReceived event is being called back, or the WaitForExit() command may be blocking the thread so it will never callback.
Any advice?
EDIT: Looks like I was trying too hard with the callback. Doing:
return p.StandardOutput.ReadToEnd();
Appears to work fine.
Here's code that I've verified to work. I use it for spawning MSBuild and listening to its output:
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data);
process.Start();
process.BeginOutputReadLine();
I just tried this very thing and the following worked for me:
StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;
outputBuilder = new StringBuilder();
processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.Arguments = "<insert command line arguments here>";
processStartInfo.FileName = "<insert tool path here>";
process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object sender, DataReceivedEventArgs e)
{
// append the new data to the data already read-in
outputBuilder.Append(e.Data);
}
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();
// use the output
string output = outputBuilder.ToString();
Here's some full and simple code to do this. This worked fine when I used it.
var processStartInfo = new ProcessStartInfo
{
FileName = #"C:\SomeProgram",
Arguments = "Arguments",
RedirectStandardOutput = true,
UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
Note that this only captures standard output; it doesn't capture standard error. If you want both, use this technique for each stream.
I needed to capture both stdout and stderr and have it timeout if the process didn't exit when expected. I came up with this:
Process process = new Process();
StringBuilder outputStringBuilder = new StringBuilder();
try
{
process.StartInfo.FileName = exeFileName;
process.StartInfo.WorkingDirectory = args.ExeDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var processExited = process.WaitForExit(PROCESS_TIMEOUT);
if (processExited == false) // we timed out...
{
process.Kill();
throw new Exception("ERROR: Process took too long to finish");
}
else if (process.ExitCode != 0)
{
var output = outputStringBuilder.ToString();
var prefixMessage = "";
throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine +
"Output from process: " + outputStringBuilder.ToString());
}
}
finally
{
process.Close();
}
I am piping the stdout and stderr into the same string, but you could keep it separate if needed. It uses events, so it should handle them as they come (I believe). I have run this successfully, and will be volume testing it soon.
It looks like two of your lines are out of order. You start the process before setting up an event handler to capture the output. It's possible the process is just finishing before the event handler is added.
Switch the lines like so.
p.OutputDataReceived += ...
p.Start();
Redirecting the stream is asynchronous and will potentially continue after the process has terminated. It is mentioned by Umar to cancel after process termination process.CancelOutputRead(). However that has data loss potential.
This is working reliably for me:
process.WaitForExit(...);
...
while (process.StandardOutput.EndOfStream == false)
{
Thread.Sleep(100);
}
I didn't try this approach but I like the suggestion from Sly:
if (process.WaitForExit(timeout))
{
process.WaitForExit();
}
You need to call p.Start() to actually run the process after you set the StartInfo. As it is, your function is probably hanging on the WaitForExit() call because the process was never actually started.
The answer from Judah did not work for me (or is not complete) as the application was exiting after the first BeginOutputReadLine();
This works for me as a complete snippet, reading the constant output of a ping:
var process = new Process();
process.StartInfo.FileName = "ping";
process.StartInfo.Arguments = "google.com -t";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
Here's a method that I use to run a process and gets its output and errors :
public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments)
{
using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }))
{
using (process.StandardOutput)
{
writer.WriteLine(process.StandardOutput.ReadToEnd());
}
using (process.StandardError)
{
writer.WriteLine(process.StandardError.ReadToEnd());
}
}
return path;
}
For example :
#"E:\Temp\MyWorkingDirectory".ShellExecute(#"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out);
I want to get output of console application in realtime (same as run via cmd.exe) by my WinForm application. All actions i perform in non-UI thread (using BackgroundWorker's method bwRezerve_DoWork). AddTextToTextbox use Invoke to update UI.
But now i receive output only when application is exited.
I read a lot of question here and on other sites, read similar question Capture output of process synchronously (i.e. "when it happens") but still can't find solution.
Here code snippet:
private void bwRezerve_DoWork(object sender, DoWorkEventArgs e)
{
proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = Application.StartupPath + Path.DirectorySeparatorChar + "7z.exe",
Arguments = e.Argument,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
}
};
proc.EnableRaisingEvents = true;
proc.OutputDataReceived += (who, what) => AddTextToTextbox(what.Data);
proc.ErrorDataReceived += (who, what) => AddTextToTextbox(what.Data);
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
//same result with next line commented
proc.WaitForExit(5 * 60 * 1000);
}
Also i've tried this instead of OutputDataReceived but result is the same
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
AddTextToTextbox(line);
}
Try this code
private void bwRezerve_DoWork(object sender, DoWorkEventArgs e)
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = Application.StartupPath + Path.DirectorySeparatorChar + "7z.exe";
psi.Arguments = e.Argument;
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
psi.CreateNoWindow = true;
Process proc = Process.Start(psi);
proc.WaitForExit();
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
AddTextToTextbox(line);
}
}
I think problem there is problem with your thread your process is running under main thread so your output will display only when process is completed.
So you need use background worker or thread you can also use dispatcher to get output from current process.
while (!proc.StandardOutput.EndOfStream)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
string line = proc.StandardOutput.ReadLine();
AddTextToTextbox(line);
}), null);
}
hope its work for you ..
EDIT
you can get current dispatcher using
window base Lib.
Assembly: WindowsBase (in WindowsBase.dll) (Ref MSDN)
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(new Action(() =>
{
string line = proc.StandardOutput.ReadLine();
AddTextToTextbox(line);
}), null);
7zip doesn't use standard output - you can easily see that since it continually rewrites the screen (to show the progress). There's no way to stream that.
I'm looking to trigger the child command window's close event once its command is finished. Keep in mind, it's a background process initiated from a console app so it's never visible. What is visible is the console application.
I tried using the Exited event, but that didn't work. I tried relying on CMD to know when to close it by using /c, /k, and exit. Neither seem to work. I also tried a do while loop checking HasExited, none of these have worked unless I type "exit" within the application console window. It does not close, but somehow triggers the invisible child command windows to close.
Is there another way of closing it once the child command is complete?
String msg = "echo %time%; exit;";
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = msg;
p.EnableRaisingEvents = true;
p.Exited += p_Exited;
p.Start();
msg += p.StandardOutput.ReadToEnd();
Thank you very much!!
I modified your program slightly to run a child command processor, capture its output, then write it to console.
char quote = '"';
string msg = "/C " + quote + "echo %time%" + quote;
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = msg;
p.EnableRaisingEvents = true;
p.Exited += (_, __) => Console.WriteLine("Exited!");
p.Start();
string msg1 = p.StandardOutput.ReadToEnd();
Console.WriteLine(msg1);
Here's a full program, using slightly different syntax, but similar in spirit:
using System;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
char quote = '"';
var startInfo = new ProcessStartInfo("cmd", "/C " + quote + "echo %time%" + quote)
{ UseShellExecute = false, RedirectStandardOutput = true };
var process = new Process { EnableRaisingEvents = true };
process.StartInfo = startInfo;
process.Exited += (_, __) => Console.WriteLine("Exited!");
process.Start();
string msg1 = process.StandardOutput.ReadToEnd();
Console.WriteLine(msg1);
Console.ReadLine();
}
}
}
Or, as this answer illustrates, maybe just call DateTimeOffset.Now. If you're interested in looking at sub-second info, maybe use Stopwatch class instead.
If you prefer to drive command line with commands from C#, it's also possible. Igor Ostrovsky describes how to convert events to Tasks; then use async/await to create a procedural-looking sequence of commands and responses.