Click here to Skip to main content
15,884,836 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hi!
I have a C# application which communicates with a console window (via SetForegroundWindow and sendKeys). This works great and I use that for sending commands to the console window (I also start another DOS program inside the command prompt).

The PROBLEM is that I would like to read back the result after each command!
I have tried to redirect standard output but that doesn't work since I need to do this with an open window for a long time.

Can anyone please point me in a correct direction? If this is possible to achieve at all?
I suspect this can be done with some API calls but that's beyond my current knowledge.

Best regards
/Cypres
Posted
Updated 27-Mar-18 0:24am
Comments
Andy Lanng 17-Apr-15 6:13am    
Does the window need to be open? can you not run a cmd command in code so the terminal doesn't hook in?

Matbe this is the linux side of me coming out but i think it's much easier if the terminal is not used. Anyway - that being case I will answer below
Johannes Grunedal 17-Apr-15 6:20am    
Hi Andy!
Thanks for quick reply. Well, I start the 'other' program from the cmd.exe command and from that I need to send and receive data. I'm not sure how you mean by not using the terminal. Could you explain please! I'm afraid Linux is not an option here.
Andy Lanng 17-Apr-15 6:28am    
It's linux terminal terminology only - I explain a bit better in my answer. Let me know if you neec clarification, but it looks like the answer suits your needs :)
Johannes Grunedal 17-Apr-15 8:22am    
Hi again!
Thanks for helping me out. It almost works, I had to do these modifications:
startInfo.RedirectStandardOutput = true;// false;
startInfo.RedirectStandardError = true; // false;
startInfo.RedirectStandardInput = true; // new

But, I can only start up the application once with one command (the process seem to close right after). I need to be able to send multiple commands and read multiple responses. I tried to break out the Process and StartInfo to make them public but that doesn't do the trick.

Regards Cypres
Andy Lanng 17-Apr-15 8:31am    
ah, then you will need to stream the buffers. This will keep the process open. I'll update with an answer.
sorry about the redirects - my code does something a little different - you are correct

There are two parts to a process run in a cmd terminal: the process and the terminal itself.

When you run a command in the terminal, that command will output to a buffer. if that buffer gets full then the process waits for the buffer to be read so it can continue.

The terminal reads from the output buffers and prints it on screen. This allows the process to continue but all that lovely output is wasted.

Take the terminal out of the equation. Forget it, it doesn't exist. This is how you get the output from the process:

C#
public class ProcessWithOutput
{
    private string _command;
    private string _args;
    private string _workingDirectory;

    public ProcessWithOutput(string command, string args, string workingDirectory)
    {
        _command = command;
        _args = args;
        _workingDirectory = workingDirectory;
    }

    public string RunId()
    {
        ProcessStartInfo startInfo = new ProcessStartInfo();
        startInfo.FileName = _command;
        startInfo.Arguments = _args;
        startInfo.WorkingDirectory = _workingDirectory;
        startInfo.UseShellExecute = false;
        startInfo.RedirectStandardOutput = false;
        startInfo.RedirectStandardError = false;

        Process process = new Process();
        process.StartInfo = startInfo;

        process.Start();

        string output = process.StandardOutput.ReadToEnd();

        //even if you don't use this you need to keep that buffer cleared
        string error = process.StandardError.ReadToEnd();

        process.WaitForExit();
        return output;
    }
}


This works great for small quick commands. If your output is huge then you can stream it instead. Let me know if you need that answer included here.

Good luck ^_^
 
Share this answer
 
I want 5 start ^_^

This is a whole class. It doesn't use any assemblies outside of the standard libraries. it can be made even nicer with RX for example, which allows you to turn events into IEnumberables in a much better way that I have done here.

If your looking for that, search for Observable.FromEvents

C#
    public class ProcessWithOutput :IDisposable
    {
        public enum PipeType { StdOut, StdErr }
        public class Output
        {
            //Theres a few ways to arrange the data.  Play with this or just output a string
            public string Message { get; set; }
            public PipeType Pipe { get; set; }
            public override string ToString()
            {
                return string.Format("{0}: {1}", Pipe, Message);
            }
        }

        private readonly string _command;
        private readonly string _args;
        private readonly string _workingDirectory;
        private Process _process;
        private bool _isDisposed = false;

        private readonly Queue<output> _outputQueue = new Queue<output>();

        private StreamWriter _inputStreamWriter;

        //These stop us rushing ahead before async threads are ready
        private ManualResetEvent[] _waitHandles = new ManualResetEvent[2];
        private ManualResetEvent _outputSteamWaitHandle = new ManualResetEvent(false);

        public ProcessWithOutput(string startCommand, string args, string workingDirectory)
        {
            _command = startCommand;
            _args = args;
            _workingDirectory = workingDirectory;
        }

        //This enumerable might be handy.  Whatever reads from it will pause when _outputSteamWaitHandle is not set, 
        //which might be ideal, or could cause your program to lock up if no output is ever received >_<        
public IEnumerable<string> GetMessages()
        {
            while (!_isDisposed)
            {
                _outputSteamWaitHandle.WaitOne();
                if (_outputQueue.Any())
                    yield return _outputQueue.Dequeue().ToString();
            }
            yield break;
        }

        // Easy interface for command input
        public void SendCommand(string command)
        {
            _inputStreamWriter.Write(command);
            _inputStreamWriter.Flush();
        }

        //You might want to start in the ctor, even ^_^
        public int StartProcess()
        {
            ProcessStartInfo startInfo = new ProcessStartInfo
            {
                FileName = _command,
                Arguments = _args,
                WorkingDirectory = _workingDirectory,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                RedirectStandardInput = true
            };

            _process = new Process { StartInfo = startInfo };


            //Outputs will be async
            _process.OutputDataReceived += delegate(object sender, DataReceivedEventArgs args)
            {
                if (args.Data == null)
                {
                    //the pipe has closed. i.e. the process has ended
                    _waitHandles[0].Set();
                }
                else if (args.Data.Length > 0)
                {
                    _outputQueue.Enqueue(new Output { Message = args.Data, Pipe = PipeType.StdOut });
                    _outputSteamWaitHandle.Set();
                }
            };
            _process.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs args)
            {
                if (args.Data == null)
                {
                    //the pipe has closed. i.e. the process has ended
                    _waitHandles[1].Set();
                }
                else if (args.Data.Length > 0)
                {
                    _outputSteamWaitHandle.Set();
                    _outputQueue.Enqueue(new Output { Message = args.Data, Pipe = PipeType.StdErr });
                }
            };

            //This will be your hook in to write commands
            _inputStreamWriter = _process.StandardInput;

            _process.Start();

            //Now the process has started and those buffers are beginning to fill.
            //These two lines start reading the buffers
            _waitHandles[0] = new ManualResetEvent(false);
            _process.BeginErrorReadLine();
            _waitHandles[1] = new ManualResetEvent(false);
            _process.BeginOutputReadLine();

            return _process.Id;
        }

        //Notice I have implemented the disposable interface to make sure that everything get cleaned up properly.
        public void Dispose()
        {
            _inputStreamWriter.Flush();
            _inputStreamWriter.Close();
            if(!_process.WaitForExit(1000))
                _process.Kill();
            //This next bit might seem odd, but trust me: You HAVE to clear those bufferes!
            if(_process.WaitForExit(1000))
                WaitHandle.WaitAll(_waitHandles);
            _isDisposed = true;
        }
    }


The explanation should be clear in the comments. Please let me know if you need any more help.

Good luck ^_^
 
Share this answer
 
v5
Comments
Member 13743237 23-Mar-18 11:41am    
If a solution doesn't work then it should get how many stars?

In your first piece of code, you wrote
StartInfo.RedirectStandardOutput = false;

> then how below piece of code can work? My Visual Studio 2017 compiler throw me an error.
string output = process.StandardOutput.ReadToEnd();

Then in 2nd code

_inputStreamWriter = _process.StandardInput;

> How can above code can run without redirect? My compiler ask me to redeirect but I dont know how to do?

And this
_inputStreamWriter.Flush();

> Throw object reference not set to an instance of an object..
Andy Lanng 27-Mar-18 10:50am    
Viola - updated below
Andy Lanng 27-Mar-18 5:37am    
tbh I wrote this 3 years ago. 2017 didn't exist (the year or the vs version :Þ)
I'll take a look, see if i remember what it does and correct it in 2017.
->
Looks like it worked for the op
Updated to 2017:

C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace ProcessWithOutput
{

    public class ProcessWrapper : Process, IDisposable
    {
        public enum PipeType { StdOut, StdErr }
        public class Output{
            
            public string Message { get; set; }
            public PipeType Pipe { get; set; }
            public override string ToString(){
                return $"{Pipe}: {Message}";
            }
        }

        private readonly string _command;
        private readonly string _args;
        private bool _isDisposed;

        private readonly Queue<Output> _outputQueue = new Queue<Output>();
        
        
        private readonly ManualResetEvent[] _waitHandles = new ManualResetEvent[2];
        private readonly ManualResetEvent _outputSteamWaitHandle = new ManualResetEvent(false);

        public ProcessWrapper(string startCommand, string args){
            _command = startCommand;
            _args = args;
        }
      
        public IEnumerable<string> GetMessages(){

            while (!_isDisposed){

                _outputSteamWaitHandle.WaitOne();
                if (_outputQueue.Any())
                    yield return _outputQueue.Dequeue().ToString();
            }
        }
        
        public void SendCommand(string command){

            StandardInput.Write(command);
            StandardInput.Flush();
        }

        public new int Start(){

            ProcessStartInfo startInfo = new ProcessStartInfo{

                FileName = _command,
                Arguments = _args,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                RedirectStandardInput = true
            };
            
            StartInfo = startInfo;
            
            OutputDataReceived += delegate (object sender, DataReceivedEventArgs args){

                if (args.Data == null){

                    _waitHandles[0].Set();
                }
                else if (args.Data.Length > 0){

                    _outputQueue.Enqueue(new Output { Message = args.Data, Pipe = PipeType.StdOut });
                    _outputSteamWaitHandle.Set();
                }
            };

            ErrorDataReceived += delegate (object sender, DataReceivedEventArgs args){

                if (args.Data == null){

                    _waitHandles[1].Set();
                }
                else if (args.Data.Length > 0){

                    _outputSteamWaitHandle.Set();
                    _outputQueue.Enqueue(new Output { Message = args.Data, Pipe = PipeType.StdErr });
                }
            };
            
            base.Start();
            
            _waitHandles[0] = new ManualResetEvent(false);
            BeginErrorReadLine();
            _waitHandles[1] = new ManualResetEvent(false);
            BeginOutputReadLine();
            
            return Id;
        }
        
        public new void Dispose(){

            StandardInput.Flush();
            StandardInput.Close();
            if (!WaitForExit(1000)){
                Kill();
            }
            if (WaitForExit(1000)){
                WaitHandle.WaitAll(_waitHandles);
            }
            base.Dispose();
            _isDisposed = true;
        }
    }
}



Test console app with output:
C#
using System;
using System.Threading;
namespace ConsoleWithOutput
{
    class Program
    {

        static void Main(string[] args){

            var count = 0;
            while (true){

                Console.Out.WriteLine($"{count++:0000} + some output");
                Thread.Sleep(1000);

            }
        }
    }
}


Test console app that uses the processwrapper:
C#
using System;

namespace ProcessWithOutput
{
    class Program
    {
        static void Main(string[] args)
        {
            var process = new ProcessWrapper(args[0],"");
            process.OutputDataReceived += (sender, eventArgs) => Console.WriteLine($"Received: {eventArgs.Data}");
            process.Start();

            Console.ReadLine();
        }
    }
}


FYI: no functional changes were made to the code. The biggest difference is that it now implements Process class instead of only wrapping it
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900