Connect Process StandardOutput to another Process StandardInput - c#

I'm trying to create a pipeline between several Process objects.
I can run a single Process and capture its StandardOutput, but when I try to connect multiple Process objects, the second one doesn't seem to receive any data from the first one's StandardInput.
In this code, I'm using cat, which either prints the contents of its arguments to stdout, or just copies stdin to stdout.
Using one process works fine:
// Works
private async void launchButtonClicked(object sender, EventArgs e)
{
var outputBuffer = new MemoryStream();
var tasks = new List<Task>();
Process process1;
{
process1 = new Process();
process1.StartInfo.FileName = "cat.exe";
process1.StartInfo.Arguments = "K:\\temp\\streams.txt";
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.RedirectStandardInput = false;
process1.StartInfo.CreateNoWindow = true;
process1.EnableRaisingEvents = true;
process1.Start();
}
tasks.Add(process1.StandardOutput.BaseStream.CopyToAsync(outputBuffer));
await Task.WhenAll(tasks);
// OK: This prints the contents of the file
Console.WriteLine("Final output: {0}", UTF8Encoding.UTF8.GetString(outputBuffer.GetBuffer()));
}
But when I add a second process and try to copy Process1's StandardOutput to Process2's StandardInput, I don't ever get any output from Process2 and the await never completes:
// Doesn't work
private async void launchButtonClicked(object sender, EventArgs e)
{
var outputBuffer = new MemoryStream();
var tasks = new List<Task>();
Process process1, process2;
{
process1 = new Process();
process1.StartInfo.FileName = "cat.exe";
process1.StartInfo.Arguments = "K:\\temp\\streams.txt";
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.RedirectStandardInput = false;
process1.StartInfo.CreateNoWindow = true;
process1.Start();
}
{
process2 = new Process();
process2.StartInfo.FileName = "cat.exe";
process2.StartInfo.Arguments = "";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.RedirectStandardOutput = true;
process2.StartInfo.RedirectStandardInput = true;
process2.StartInfo.CreateNoWindow = true;
process2.Start();
}
tasks.Add(process1.StandardOutput.BaseStream.CopyToAsync(process2.StandardInput.BaseStream));
tasks.Add(process2.StandardOutput.BaseStream.CopyToAsync(outputBuffer));
await Task.WhenAll(tasks); // Never returns!
Console.WriteLine("Final output: {0}", UTF8Encoding.UTF8.GetString(outputBuffer.GetBuffer()));
}
Doing something like this works fine from the command line:
C:\>cat.exe K:\temp\streams.txt | cat.exe
... contents of file ...
C:\>
I tried adding Exited event handlers to these Process objects. Process1 exits fine, but Process2 never exits.
I tried some other commands too (like sed), but Process2 still never seems to do anything.
I'm using the streams' BaseStream properties, because I will eventually be working with binary data. (https://stackoverflow.com/a/4535927/339378) This also means I can't use OutputDataReceived, which returns a string (and that would be more complicated anyway).
Thanks!

Apparently, CopyToAsync doesn't close the output stream on completion. This didn't matter for my MemoryStream, but obviously I need to close a process's StandardInput or it won't ever complete.
I made this method:
private static async Task CopyThenClose(Stream from, Stream to)
{
await from.CopyToAsync(to);
to.Close();
}
And replaced the calls to CopyToAsync; eg.:
tasks.Add(CopyThenClose(process1.StandardOutput.BaseStream, process2.StandardInput.BaseStream));

Related

how to get Powershell output of this code? [duplicate]

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;
}
}

How to run process in background? c#

I am using wkhtmltopdf for reporting purpose in my application. However it works great as expected.
Problem-
In below method for I am converting strings source to pdf and writing bytes.
Note-
I am starting process before reading all bytes and trying to run a process.
The main problem is background ability of running this process. I don't get this process to run in background.
Currently what happens is until pdf is not generated the whole application goes in halt mode.This is something to do with background process is not working.
How do I modify this process so it works in background without halting my application?
I have read about task factory and multiple threads but I didn't get the clue.
Method for pdf conversion-
public byte[] ConverToPdf(string source, string commandLocation)
{
string HtmlToPdfExePath = Server.MapPath("~/wkhtmltopdf.exe");
Process p;
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = Path.Combine(commandLocation, HtmlToPdfExePath);
psi.WorkingDirectory = Path.GetDirectoryName(psi.FileName);
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
string args = "-q -n ";
args += "--enable-javascript ";
args += "--enable-plugins ";
args += "--disable-smart-shrinking ";
args += "--margin-bottom 20 ";
args += "--margin-left 20 ";
args += "--margin-right 20 ";
args += "--margin-top 20 ";
args += "--orientation Landscape ";
args += "--outline-depth 0 ";
args += "--page-size A4 ";
args += "--encoding utf-8";
args += " - -";
psi.Arguments = args;
p = Process.Start(psi);
try
{
using (StreamWriter stdin = new StreamWriter(p.StandardInput.BaseStream, Encoding.UTF8))
{
stdin.AutoFlush = true;
stdin.Write(source);
}
byte[] buffer = new byte[32768];
byte[] file;
using (var ms = new MemoryStream())
{
while (true)
{
int read = p.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length);
if (read <= 0)
break;
ms.Write(buffer, 0, read);
}
file = ms.ToArray();
}
p.StandardOutput.Close();
p.WaitForExit(60000);
int returnCode = p.ExitCode;
p.Close();
if (returnCode == 0)
return file;
else
return file;
}
catch (Exception ex)
{
ModelState.AddModelError("Could not create PDF", ex);
}
finally
{
p.Close();
p.Dispose();
}
return null;
}
Update-
I was actually trying to find a way of background worker to do its job and return bytes from above method.
My main purpose of calling this method with background worker is Communicating back to from where it was called with results.
public ActionResult DuesPDF()
{
var relativePath = "~/Views/Shared/_TestView.cshtml";
string content;
var view = ViewEngines.Engines.FindView(ControllerContext, relativePath, null);
using (var writer = new StringWriter())
{
TempData["req"] = "DuesAndOverDuesChart";
var context = new ViewContext(ControllerContext, view.View, ViewData, TempData, writer);
view.View.Render(context, writer);
writer.Flush();
content = writer.ToString();
byte[] pdfBuf = ConverToPdf(content, Server.MapPath("~/PDF/"));
if (pdfBuf == null)
return null;
return File(pdfBuf, "application/pdf");
}
}
Here is in this method I am calling pdf method- ConverToPdf(content, Server.MapPath("~/PDF/"))
Note-
I am asking for communication from ConverToPdf method above with background worker.
Use Thread Class it will run your method in background thread other than the main thread.
More over you are returning null in your method so its better to make the return type void and do not return null.
Thread threadObj = new Thread(new ThreadStart(()=>ConverToPdf("a","b")));
threadObj.Start();
Update : I am assuming you are using windows form. As per your updates you can easily see that your main method need the response to execute further. now for this i would suggest you to show some progress bar in background thread so that i would not halt the application. and when the converttoPDF method finishes it process stop the progress bar. in this case your application would not stuck and you will get the response as you need.
private void Button1_Click(object sender, EventArgs e)
{
bgworker.RunWorkerAsync();
}
private void bgworker_DoWork(object sender, DoWorkEventArgs e)
{
ConverToPdf(source, commandLocation);
}

UI Freeze using external Process

I have implemented the following code (adapted from the tutorial) in order to run a command prompt window, run a program and read the output. The code is called from a ButtonClick event handler, which is nested in a User Control.
I was under the impression that this would allow the rest of my program to function whilst the external process runs, due to the fact that the methods are 'asynchronous'. However, this does not appear to be the case, as my UI will freeze while the operation is running. I should add that the output received when the cmd process ends is correct.
Sorry to dump a load of code like this, just not sure what else to do at this point!
Any assistance would be greatly appreciated.
public static void runExternalProcess()
{
StringBuilder output = new StringBuilder();
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.OutputDataReceived += new DataReceivedEventHandler(outputEventHandler);
cmd.StartInfo.RedirectStandardInput = true;
cmd.Start();
cmd.BeginOutputReadLine();
StreamWriter sortStreamWriter = cmd.StandardInput;
StreamWriter sw = cmd.StandardInput;
if (sw.BaseStream.CanWrite)
{
sw.WriteLine("ping www.google.com");
}
sw.Close();
cmd.WaitForExit();
MessageBox.Show(output.ToString());
cmd.Close();
}
private static void outputEventHandler(object sendingProcess, DataReceivedEventArgs e)
{
if (!String.IsNullOrEmpty(e.Data))
{
output.Append(e.Data + Environment.NewLine);
}
}
How about registering for the Exited event and showing the MessageBox there:
StringBuilder output = new StringBuilder();
Process cmd = new Process();
public void RunExternalPing()
{
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardInput = true;
cmd.EnableRaisingEvents = true;
cmd.OutputDataReceived +=
new DataReceivedEventHandler(cmd_OutputDataReceived);
cmd.Exited += new EventHandler(cmd_Exited);
cmd.Start();
cmd.BeginOutputReadLine();
StreamWriter sw = cmd.StandardInput;
sw.WriteLine("ping www.google.com");
sw.Close();
}
void cmd_Exited(object sender, EventArgs e)
{
MessageBox.Show(output.ToString());
cmd.Dispose();
}
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (!String.IsNullOrEmpty(e.Data))
{
output.Append(e.Data + Environment.NewLine);
}
}
From MSDN:
There are two ways of being notified when the associated process
exits: synchronously and asynchronously. Synchronous notification
relies on calling the WaitForExit method to pause the processing of
your application until the associated component exits. Asynchronous
notification relies on the Exited event. In either case,
EnableRaisingEvents must be set to true for the Process component to
receive notification that the process has exited.
Your problem is here:
cmd.WaitForExit();
This is a blocking call.
If you want to respond to the process exiting without blocking then you need to add a handler for the Exited event.
All this code is linear, if you don't want to freeze the thread you're in, you should create a new thread and perform a callback when that thread is finished.
Check out BackgroundWorker.

Realtime Console Output Redirection using Process

I am using VBOXMANAGE to "export" a guest machine. VBOXManage is a Console application that can control the guest machine's behavior from the host. Since the export command is a long process, it returns process updates like so:
0%...10%...20%...30%...100%
I am writing a C# application that will invoke VBOXManage using Process. Here's my code:
Process VBOXProc = new Process();
VBOXProc.StartInfo.FileName = VBOXMANAGE;
VBOXProc.StartInfo.Arguments = Arguments;
VBOXProc.StartInfo.UseShellExecute = false;
VBOXProc.StartInfo.CreateNoWindow = true;
VBOXProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
VBOXProc.StartInfo.RedirectStandardError = true;
VBOXProc.StartInfo.RedirectStandardOutput = true;
VBOXProc.OutputDataReceived += new DataReceivedEventHandler(VBOXProc_OutputDataReceived);
VBOXProc.ErrorDataReceived += new DataReceivedEventHandler(VBOXProc_ErrorDataReceived);
VBOXProc.EnableRaisingEvents = true;
VBOXProc.Start();
VBOXProc.BeginOutputReadLine();
VBOXProc.BeginErrorReadLine();
VBOXProc.WaitForExit();
This is fine, except that the output is being read per LINE. This means that the process updates "
0%...10%...20%...30%...100%" will only show AFTER the actual process is done.
Is there a way to capture the console output in realtime?
Thanks!
This worked for me:
process.StartInfo.CreateNoWindow = true;
process.StartInfo.ErrorDialog = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.ErrorDataReceived += (sendingProcess, errorLine) => error.AppendLine(errorLine.Data);
process.OutputDataReceived += (sendingProcess, dataLine) => SetMessage(dataLine.Data);
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
error.AppendLine() and SetMessage() are the methods I used.
You can read directly from the StanadardOutput/Error for the process using all the standard Stream methods, just be sure to set the StartInfo.Redirectxxx to true.
var p = new Process()
p.StartInfo.UseShellExecute = false; //not sure if this is absolutely required
p.StartInfo.RedirectStandardOuput = true;
....
do
{
Thread.Sleep(nnn);
Console.Out.Write(p.StandardOutput.ReadToEnd());
}
while (!p.HasExited);
//catch any leftovers in redirected stdout
Console.Out.Write(p.StandardOutput.ReadToEnd());
The above will echo the output of the child process to your applications Standard Out.
You can read Blocks of a particular size using p.StandardOutput.Read(char[], int, int) or asynchronous reads using p.StadardOutput.BaseStream.BeginRead(...).
All the same methods are available for StandardError.
Sleeping in the loop frees up the processor for other tasks and allows some data to accumulate in the bufffer. If the sleep period is too long and the buffer overflows some output from the executing process will be lost. If the sleep period is too short a lot of CPU cycles are spent reading and empty buffer.
Try to redirect standard input too and apply AutoFlush to StandardInput. Next read stream using StreamReader.
Process proces;
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "test.exe";
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
proces = Process.Start(psi);
proces.StandardInput.AutoFlush = true;
Sorry any mistake, I am Brazilian and to using Google Translate to write this text.
Coincidentally, I also'm doing a program that works with VBoxManage of Virtualbox. In my case I wanted, among other things, convert a virtual disk. Also it delays and the percentage with progress also
I managed to do just this by creating a process of will to run the program, and using a user classes 'Dean North` the other question that is similar to this. It is important to use a thread to run the VBoxManage, otherwise has no way to work the obtained text or view the progress.
O texto é muito grande pra eu adicionar quatro espaços antes de cada linha e repassar.
The classes replace the Process system class. Need not make any changes to your code, just add a arquivo.cs with the text passed by the user Dean North instead of Process p = new Process() use FixedProcess p = new FixedProcess ()
After that it was my code:
private void BotaoParaTestes_Click(object sender, EventArgs e)
{
string linha = #"clonehd " +
"\"Z:\\Máquinas Virtuais\\Teste.vdi\" " +
"\"C:\\Temp\\teste.vdi\" " +
"--format VDI --variant Standard";
Thread tarefa = new Thread(Executar);
tarefa.Start(linha);
}
private void Executar(object Linha)
{
FixedProcess fp = new FixedProcess ();
fp.StartInfo.FileName = ItensEstaticos.VBox;
fp.StartInfo.Arguments = Linha.ToString();
fp.StartInfo.CreateNoWindow = true;
fp.StartInfo.ErrorDialog = false;
fp.StartInfo.RedirectStandardError = true;
fp.StartInfo.RedirectStandardOutput = true;
fp.StartInfo.UseShellExecute = false;
fp.ErrorDataReceived += (sendingProcess, errorLine) => Escrita(errorLine.Data);
fp.OutputDataReceived += (sendingProcess, dataLine) => Escrita(dataLine.Data);
fp.Start();
fp.BeginErrorReadLine();
fp.BeginOutputReadLine();
fp.WaitForExit();
}
private void Escrita(string Texto)
{
if (!string.IsNullOrEmpty(Texto))
{
BeginInvoke(new MethodInvoker(delegate
{
this.Texto.Text += Texto;
}));
}
}
For me the event is only called when the text is changed, not only when the VBoxManage goes to a new line. Sometimes the text was null, then place a check structure as I did before using the text obtained for controls.

Capturing console output from a .NET application (C#)

How do I invoke a console application from my .NET application and capture all the output generated in the console?
(Remember, I don't want to save the information first in a file and then relist as I would love to receive it as live.)
This can be quite easily achieved using the ProcessStartInfo.RedirectStandardOutput property. A full sample is contained in the linked MSDN documentation; the only caveat is that you may have to redirect the standard error stream as well to see all output of your application.
Process compiler = new Process();
compiler.StartInfo.FileName = "csc.exe";
compiler.StartInfo.Arguments = "/r:System.dll /out:sample.exe stdstr.cs";
compiler.StartInfo.UseShellExecute = false;
compiler.StartInfo.RedirectStandardOutput = true;
compiler.Start();
Console.WriteLine(compiler.StandardOutput.ReadToEnd());
compiler.WaitForExit();
This is bit improvement over accepted answer from #mdb. Specifically, we also capture error output of the process. Additionally, we capture these outputs through events because ReadToEnd() doesn't work if you want to capture both error and regular output. It took me while to make this work because it actually also requires BeginxxxReadLine() calls after Start().
Asynchronous way:
using System.Diagnostics;
Process process = new Process();
void LaunchProcess()
{
process.EnableRaisingEvents = true;
process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_OutputDataReceived);
process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_ErrorDataReceived);
process.Exited += new System.EventHandler(process_Exited);
process.StartInfo.FileName = "some.exe";
process.StartInfo.Arguments = "param1 param2";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
//below line is optional if we want a blocking call
//process.WaitForExit();
}
void process_Exited(object sender, EventArgs e)
{
Console.WriteLine(string.Format("process exited with code {0}\n", process.ExitCode.ToString()));
}
void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data + "\n");
}
void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data + "\n");
}
Use ProcessStartInfo.RedirectStandardOutput to redirect the output when creating your console process.
Then you can use Process.StandardOutput to read the program output.
The second link has a sample code how to do it.
ConsoleAppLauncher is an open source library made specifically to answer that question. It captures all the output generated in the console and provides simple interface to start and close console application.
The ConsoleOutput event is fired every time when a new line is written by the console to standard/error output. The lines are queued and guaranteed to follow the output order.
Also available as NuGet package.
Sample call to get full console output:
// Run simplest shell command and return its output.
public static string GetWindowsVersion()
{
return ConsoleApp.Run("cmd", "/c ver").Output.Trim();
}
Sample with live feedback:
// Run ping.exe asynchronously and return roundtrip times back to the caller in a callback
public static void PingUrl(string url, Action<string> replyHandler)
{
var regex = new Regex("(time=|Average = )(?<time>.*?ms)", RegexOptions.Compiled);
var app = new ConsoleApp("ping", url);
app.ConsoleOutput += (o, args) =>
{
var match = regex.Match(args.Line);
if (match.Success)
{
var roundtripTime = match.Groups["time"].Value;
replyHandler(roundtripTime);
}
};
app.Run();
}
I've added a number of helper methods to the O2 Platform (Open Source project) which allow you easily script an interaction with another process via the console output and input (see http://code.google.com/p/o2platform/source/browse/trunk/O2_Scripts/APIs/Windows/CmdExe/CmdExeAPI.cs)
Also useful for you might be the API that allows the viewing of the console output of the current process (in an existing control or popup window). See this blog post for more details: http://o2platform.wordpress.com/2011/11/26/api_consoleout-cs-inprocess-capture-of-the-console-output/ (this blog also contains details of how to consume the console output of new processes)
I made a reactive version that accepts callbacks for stdOut and StdErr.
onStdOut and onStdErr are called asynchronously,
as soon as data arrives (before the process exits).
public static Int32 RunProcess(String path,
String args,
Action<String> onStdOut = null,
Action<String> onStdErr = null)
{
var readStdOut = onStdOut != null;
var readStdErr = onStdErr != null;
var process = new Process
{
StartInfo =
{
FileName = path,
Arguments = args,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = readStdOut,
RedirectStandardError = readStdErr,
}
};
process.Start();
if (readStdOut) Task.Run(() => ReadStream(process.StandardOutput, onStdOut));
if (readStdErr) Task.Run(() => ReadStream(process.StandardError, onStdErr));
process.WaitForExit();
return process.ExitCode;
}
private static void ReadStream(TextReader textReader, Action<String> callback)
{
while (true)
{
var line = textReader.ReadLine();
if (line == null)
break;
callback(line);
}
}
Example usage
The following will run executable with args and print
stdOut in white
stdErr in red
to the console.
RunProcess(
executable,
args,
s => { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(s); },
s => { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(s); }
);
From PythonTR - Python Programcıları Derneği, e-kitap, örnek:
Process p = new Process(); // Create new object
p.StartInfo.UseShellExecute = false; // Do not use shell
p.StartInfo.RedirectStandardOutput = true; // Redirect output
p.StartInfo.FileName = "c:\\python26\\python.exe"; // Path of our Python compiler
p.StartInfo.Arguments = "c:\\python26\\Hello_C_Python.py"; // Path of the .py to be executed
Added process.StartInfo.**CreateNoWindow** = true; and timeout.
private static void CaptureConsoleAppOutput(string exeName, string arguments, int timeoutMilliseconds, out int exitCode, out string output)
{
using (Process process = new Process())
{
process.StartInfo.FileName = exeName;
process.StartInfo.Arguments = arguments;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
output = process.StandardOutput.ReadToEnd();
bool exited = process.WaitForExit(timeoutMilliseconds);
if (exited)
{
exitCode = process.ExitCode;
}
else
{
exitCode = -1;
}
}
}

Categories

Resources