Reputation: 7170
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:
Upvotes: 415
Views: 442612
Reputation: 376
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);
Upvotes: 3
Reputation: 12242
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);
Upvotes: 398
Reputation: 946
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);
}
Upvotes: 28
Reputation: 1785
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 [email protected] --encrypt " + archivo;
try{
System.IO.File.WriteAllText(archivo, xml);
//String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient [email protected] --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 };
}
}
Upvotes: 1
Reputation: 51
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) {
}
Upvotes: 4
Reputation: 100748
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.
Upvotes: 581
Reputation: 346
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
Upvotes: 2
Reputation: 11886
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 :)
Upvotes: 6
Reputation: 276303
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 :)
Upvotes: 6
Reputation: 164341
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.
Upvotes: 10