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 a free trial launcher for the Final Fantasy XIV game. Now I want to launch it through process.start in a windows service in C#. the process starts successfully, as I can see it in the windows task manager, but it does not launch the setup file. Here is the code I've tried.
try {
var process = new Process();
var startInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Normal,
FileName = "cmd.exe",
Arguments = "/C " + setupFile + " /DIR=" + installLocation,
};
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
}
catch (Exception ex)
{
Logger.Log.Error("Error Installing game.", ex);
}
Where setupfile is the complete path for ffxivsetup_ft.exe.
Try this code :
ProcessStartInfo startInfo = new ProcessStartInfo(path);
Process.Start(startInfo);
I am writing an app to run ffmpeg using c#. My program redirects the standardError output to a stream so it can be parsed for progress information.
During testing I have found a problem:
If the output is shown in a command window rather than being redirected ffmpeg will display it's normal headers followed by "file c:\temp\testfile.mpg already exists. overwrite [y]". If I click on the command window and press y the program continues to encode the file.
If the StandardError is redirected to my handler and then printed to the console, I see the same header information that was displayed in the command window now printed to the console. except for the file...already exists prompt. If I click in the command window and press y the program continues to process the file.
Is there a stream other than standardOutput or standardError that is used when the operator is prompted for information, or am I missing something else?
public void EncodeVideoWithProgress(string filename, string arguments, BackgroundWorker worker, DoWorkEventArgs e)
{
Process proc = new Process();
proc.StartInfo.FileName = "ffmpeg";
proc.StartInfo.Arguments = "-i " + " \"" + filename + "\" " + arguments;
proc.StartInfo.UseShellExecute = false;
proc.EnableRaisingEvents = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardOutput = false;
proc.StartInfo.CreateNoWindow = false; //set to true for testing
proc.ErrorDataReceived += new DataReceivedEventHandler(NetErrorDataHandler);
proc.Start();
proc.BeginErrorReadLine();
StreamReader reader = proc.StandardOutput;
string line;
while ((line = reader.ReadLine()) != null)
{ Console.WriteLine(line); }
proc.WaitForExit();
}
private static void NetErrorDataHandler(object sendingProcess,
DataReceivedEventArgs errLine)
{
if (!String.IsNullOrEmpty(errLine.Data))
{
Console.WriteLine(errLine.Data);
}
}
Rather than going through all this stuff, use the "-y" command line option when you start the process, which will force ffmpeg to overwrite existing files.
I have a console application that asks for a SourcePath when started.. when I enter the Source Path, It asks for DestinationPath... when i enter DestinationPath it starts some execution
My Problem is to Supply these path via a windows application, means i need to create a window form application that will supply these parameters to the console application automatiocally after certain time interval
can it be achieved or not... if yes, plese help... its very Urgent...
ohh..
I have tried a lot of code that i can not paste hear all but some that i use to start the application are...
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = #"C:\Program Files\Wondershare\PPT2Flash SDK\ppt2flash.exe";
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardInput = true;
psi.CreateNoWindow = false;
psi.Arguments = input + ";" + output;
Process p = Process.Start(psi);
and
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
FileName = #"C:\Program Files\Wondershare\PPT2Flash SDK\ppt2flash.exe",
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
}
};
if (process.Start())
{
Redirect(process.StandardError, text);
Redirect(process.StandardOutput, text);
MessageBox.Show(text);
}
private void Redirect(StreamReader input, string output)
{
new Thread(a =>{var buffer = new char[1];
while (input.Read(buffer, 0, 1) > 0)
{
output += new string(buffer);
};
}).Start();
}
but nothing seems to be working
You can add parameters to your ProcessStartInfo like this:
ProcessStartInfo psi = new ProcessStartInfo(#"C:\MyConsoleApp.exe",
#"C:\MyLocationAsFirstParamter C:\MyOtherLocationAsSecondParameter");
Process p = Process.Start(psi);
this will startup the console app with 2 parameters.
Now in your console app you have the
static void Main(string[] args)
the string array args is what contains the parameters, now all you have to do is get them when your app starts.
if (args == null || args.Length < 2)
{
//the arguments are not passed correctly, or not at all
}
else
{
try
{
yourFirstVariable = args[0];
yourSecondVariable = args[1];
}
catch(Exception e)
{
Console.WriteLine("Something went wrong with setting the variables.")
Console.WriteLine(e.Message);
}
}
This may or may not be the exact code that you need, but at least will give you an insight in how to accomplish what you want.
I have to start an executable (installPrint.exe) within my C# code. For this purposes I used the System.Diagnostics.Process class. The exe file installs a printer driver and copy several files into different directories. I can execute the exe from command line and everything work fine. But if i execute the file with the Process class from my C# application, the printer driver will not be installed.
I start my C# application as a admin user on a Windows XP SP2 x86 machine. Why do my executable dont work in the context of my C# application? What possibilities do i have to get it work?
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.Arguments = "-i \"My Printer\" -dir . -port myPort -spooler";
startInfo.CreateNoWindow = true;
startInfo.FileName = #"C:\Printer\install.exe";
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
//startInfo.Verb = "runas";
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.WorkingDirectory = #"C:\Printer\";
session.Log("Working Directory: " + startInfo.WorkingDirectory);
session.Log("Executing " + startInfo.FileName);
try
{
Process process = new Process();
//process.EnableRaisingEvents = false;
process.StartInfo = startInfo;
process.Start();
session.Log("installer.exe started");
StreamReader outReader = process.StandardOutput;
StreamReader errReader = process.StandardError;
process.WaitForExit();
//session.Log(outReader.ReadToEnd());
//session.Log(errReader.ReadToEnd());
session.Log("RETURN CODE: " + process.ExitCode);
}
catch (Exception ex)
{
session.Log("An error occurred during printer installation.");
session.Log(ex.ToString());
}
I take it, you are running your program on Windows Vista or 7. Then, you have to request elevation for your newly created process to run with full access rights. Look at those questions for details:
Request Windows Vista UAC elevation if path is protected?
Windows 7 and Vista UAC - Programmatically requesting elevation in C#
Ok, I see now, that you're using Win XP. Then it may be because of some settings of Process when you start it. Try to start you process as ShellExecute, this way it will be most close to normal starting by the user.
Here's a sample:
var p = new System.Diagnostics.Process();
p.StartInfo = new System.Diagnostics.ProcessStartInfo { FileName = "yourfile.exe", UseShellExecute = true };
p.Start();
I use this class in many parts of my projects:
public class ExecutableLauncher
{
private string _pathExe;
public ExecutableLauncher(string pathExe)
{
_pathExe = pathExe;
}
public bool StartProcessAndWaitEnd(string argoment, bool useShellExecute)
{
try
{
Process currentProcess = new Process();
currentProcess.EnableRaisingEvents = false;
currentProcess.StartInfo.UseShellExecute = useShellExecute;
currentProcess.StartInfo.FileName = _pathExe;
// Es.: currentProcess.StartInfo.Arguments="http://www.microsoft.com";
currentProcess.StartInfo.Arguments = argoment;
currentProcess.Start();
currentProcess.WaitForExit();
currentProcess.Close();
return true;
}
catch (Exception currentException)
{
throw currentException;
}
}
}
I hope to have answered at your question.
M.