400

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:

  1. To get the command line shell output, and write it on my text box?
  2. To get the numerical value to show a progress bar with time elapsed?
Sri Harsha Chilakapati
  • 11,744
  • 6
  • 50
  • 91
stighy
  • 7,260
  • 25
  • 97
  • 157

10 Answers10

566

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.

meJustAndrew
  • 6,011
  • 8
  • 50
  • 76
Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • 6
    I was wondering how you could deal with StandardError ?. BTW I really like this code snippet ! nice and clean. – codea Nov 27 '13 at 08:54
  • 3
    Thanks,But I think I was not clear: Should I add another loop to do so ? – codea Nov 27 '13 at 13:26
  • @codea - I see. You can create one loop that terminates when both streams reach EOF. That can get a little complicated because one stream will inevitably hit EOF first and you don't want to read from it anymore. You could also use two loops in two different threads. – Ferruccio Nov 27 '13 at 13:47
  • 2
    is it more robust to read until the process itself terminates, rather than waiting for end of streams? – Gusdor Jan 07 '14 at 10:39
  • 2
    @Gusdor - I don't think so. When the process terminates, its streams will automatically be closed. Also, a process may close its streams long before it terminates. – Ferruccio Jan 07 '14 at 11:54
  • 1
    I am trying to use this code on Ffmpeg, any help, stuck in find out the processes work is completed. – Anirudha Gupta Jul 11 '18 at 08:16
375

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);
Legends
  • 21,202
  • 16
  • 97
  • 123
T30
  • 11,422
  • 7
  • 53
  • 57
  • 5
    gotta love async! I was able to use this code (with a little transcribing) in VB.net – Richard Barker Nov 24 '15 at 17:06
  • note 'string output = process.StandardOutput.ReadToEnd();' may produce a large string if there is many lines of output; the async example, and the answer from Ferruccio both process the output line by line. – Andrew Hill May 25 '17 at 23:34
  • 9
    **Note:** your first (synchronous) approach is not correct! You should NOT read both StandardOutput and StandardError synchronously! it will cause dead-locks. atleast one of them must be async. – S.Serpooshan Jan 01 '18 at 08:44
  • 14
    Process.WaitForExit() is thread blocking, thus synchronous. Not the point of the answer, but I thought I might add this. Add process.EnableRaisingEvents = true and make use of the Exited event to be fully asynchronous. – Tom Jan 02 '18 at 12:55
  • Is it not possible to directly redirect? I use all colorization of the sass output? – Ini Aug 20 '19 at 22:27
  • 1
    You are missing a dispose. – Samuel Oct 29 '19 at 19:14
  • @S.Serpooshan Can you explain this? I wouldn't know why that should be the case. To T30: The above async code isn't safe if you need to take the full output when the process exits. Those events keep coming in after the process has exited and the program has moved on. There's no indication whether the data is complete. If in doubt, the output data will always be incomplete! Seen this here. I'm not sure if async is a good solution in this case. – ygoe Aug 01 '20 at 13:32
  • Probably worth mentioning that the Standard Error of the child process ought to go to the Standard Error of this application (e.g. via `using (var standardErrorStreamWriter = new StreamWriter(Console.OpenStandardError())) standardErrorStreamWriter.Write(e.Data);`). `Console.WriteLine` writes to the Standard Output instead. – Sphynx Oct 19 '21 at 23:56
  • 1
    This is fantastic. Similar to @RichardBarker, I too was able to use this with some transcribing into VB.Net, and this works EXACTLY how I needed it to! Adding Event Handlers for each `OutputDataReceived` and `ErrorDataReceived` and appending the data to Public StringBuilders (using them for multiple shell commands) has allowed me to hook the StdOut/StdErr data and process it to provide feedback to my users! WONDERFUL STUFF! – k1dfr0std Jan 19 '22 at 23:13
26

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);
}
Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
cubrman
  • 884
  • 1
  • 9
  • 22
  • Thanks for adding this. Can I ask what command you were using? – T30 Feb 14 '17 at 14:50
  • I am developing an app in c# that is designed to launch a mysqldump.exe, show the user every single message the app generates, wait for it to finish and then perform some more tasks. I can't understand what kind of command you are talking about? This entire question is about launching a process from c#. – cubrman Feb 20 '17 at 12:09
  • 1
    if you use two separate handlers you will not get deadlocks – ovi Feb 15 '18 at 19:05
  • also in you example, you read the process.StandardOutput only once... right after you start it, but one would want to read it continuously while the process is running, no? – ovi Feb 15 '18 at 19:09
  • @Curbman, I think T30 was asking "what command" because you are firing the process called "cmd.exe" . – Eric Wood Oct 05 '20 at 13:47
  • Strongly related: [ProcessStartInfo hanging on “WaitForExit”? Why?](https://stackoverflow.com/q/139593/1364007) – Wai Ha Lee May 26 '21 at 16:42
10

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.

driis
  • 161,458
  • 45
  • 265
  • 341
6

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 :)

shakram02
  • 10,812
  • 4
  • 22
  • 21
6
  1. 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

  2. This depends on mencoder. If it ouputs this status on the command line then yes :)

stema
  • 90,351
  • 20
  • 107
  • 135
basarat
  • 261,912
  • 58
  • 460
  • 511
3

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) { 
}
3

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);
Jason Harris
  • 376
  • 2
  • 12
2

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

abberdeen
  • 323
  • 7
  • 32
1

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 };
            }
        }
Jorge Santos Neill
  • 1,635
  • 13
  • 6
  • exceptions caught by general catch(Exception) must be re-thrown, otherwise it swallows the exception which can be awaited by "upper" code. In the given example the debugger will not stop on exception if it happened inside `try` block – Evgeny Gorbovoy Aug 20 '21 at 16:48