5

I'm trying to make a console application, that can wrap around pretty much any application that opens a console.

A few quick facts:

  • The output of the wrapped application should show in the console.
  • The input of the wrapper console should also be the input of the wrapped console app.
  • You must be able to use code to also insert commands.

This is what I have so far, everything works, except the last few lines:

ProcessStartInfo startInfo = new ProcessStartInfo("someBatchThingy.cmd");

startInfo.WorkingDirectory = Environment.CurrentDirectory;
startInfo.UseShellExecute = false;

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

process.Start();

Thread.Sleep(5000);

Stream s = Console.OpenStandardOutput();
byte[] stopCommand = new byte[] { 115, 116, 111, 112, 13 };
s.Write(stopCommand, 0, stopCommand.Length);

process.WaitForExit();

Because performance matters a lot, I really want to reassign to where the process outputs, and not manually transfer the data from a hidden console, to the wrapper console.

Does anyone know how to do this/if this is possible?

Aidiakapi
  • 6,034
  • 4
  • 33
  • 62
  • 1
    Check this thread: [Capturing Console output from a .NET app](http://stackoverflow.com/questions/186822/capturing-console-output-from-a-net-application-c). – vgru Sep 11 '11 at 09:59
  • @Groo Sorry, that's exactly what I'm not looking for, I don't want to redirect the console output/input, I just want to be able to add some input to it myself. – Aidiakapi Sep 11 '11 at 10:02
  • You are writing to your own console, not the stdin of the process you started. Redirecting its input is needed to do this. Yes, not what you want to do. Next stop is the SendKeys class or pinvoking SendInput(), correct focus is really important. – Hans Passant Sep 11 '11 at 13:03
  • A hand on the pinvoking would be nice :P – Aidiakapi Sep 11 '11 at 19:50
  • @Aidiakapi: [SendInput P/Invoke](http://pinvoke.net/default.aspx/user32/SendInput.html). But IMHO [`SendKeys`](http://msdn.microsoft.com/en-us/library/k3w7761b(v=VS.90).aspx) should be an adequate managed wrapper if you don't need to send complex mouse messages. – vgru Sep 12 '11 at 09:56
  • I'm sorry, I've never practiced the native libraries for this. Do you have any idea how to send `"stop\r\n"` to the process that's running inside the console? Maybe you could make it an answer :) – Aidiakapi Sep 12 '11 at 10:29
  • Old thread but I've had a similar requirement and came across this (my solution is in an answer below), the problem with this code though is that it writes the 'stop' command to Standard Output (i.e the console window) not the standard input where user entered commands typically come from. – Chris Lee May 27 '15 at 20:04

3 Answers3

4

So as far as I know there is no way to achieve this without tunneling the data through your application.

Code to do this is as follows:

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace TestApp
{
    internal static class Program
    {
        [MTAThread]
        public static void Main(string[] args)
        {
            const string fileName = @"..\..\..\ChildConsoleApp\bin\Debug\ChildConsoleApp.exe";

            // Fires up a new process to run inside this one
            var process = Process.Start(new ProcessStartInfo
            {
                UseShellExecute = false,

                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,

                FileName = fileName
            });

            // Depending on your application you may either prioritize the IO or the exact opposite
            const ThreadPriority ioPriority = ThreadPriority.Highest;
            var outputThread = new Thread(outputReader) { Name = "ChildIO Output", Priority = ioPriority};
            var errorThread = new Thread(errorReader) { Name = "ChildIO Error", Priority = ioPriority };
            var inputThread = new Thread(inputReader) { Name = "ChildIO Input", Priority = ioPriority };

            // Set as background threads (will automatically stop when application ends)
            outputThread.IsBackground = errorThread.IsBackground
                = inputThread.IsBackground = true;

            // Start the IO threads
            outputThread.Start(process);
            errorThread.Start(process);
            inputThread.Start(process);

            // Demonstrate that the host app can be written to by the application
            process.StandardInput.WriteLine("Message from host");

            // Signal to end the application
            ManualResetEvent stopApp = new ManualResetEvent(false);

            // Enables the exited event and set the stopApp signal on exited
            process.EnableRaisingEvents = true;
            process.Exited += (e, sender) => { stopApp.Set(); };

            // Wait for the child app to stop
            stopApp.WaitOne();

            // Write some nice output for now?
            Console.WriteLine();
            Console.Write("Process ended... shutting down host");
            Thread.Sleep(1000);
        }

        /// <summary>
        /// Continuously copies data from one stream to the other.
        /// </summary>
        /// <param name="instream">The input stream.</param>
        /// <param name="outstream">The output stream.</param>
        private static void passThrough(Stream instream, Stream outstream)
        {
            byte[] buffer = new byte[4096];
            while (true)
            {
                int len;
                while ((len = instream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    outstream.Write(buffer, 0, len);
                    outstream.Flush();
                }
            }
        }

        private static void outputReader(object p)
        {
            var process = (Process)p;
            // Pass the standard output of the child to our standard output
            passThrough(process.StandardOutput.BaseStream, Console.OpenStandardOutput());
        }

        private static void errorReader(object p)
        {
            var process = (Process)p;
            // Pass the standard error of the child to our standard error
            passThrough(process.StandardError.BaseStream, Console.OpenStandardError());
        }

        private static void inputReader(object p)
        {
            var process = (Process)p;
            // Pass our standard input into the standard input of the child
            passThrough(Console.OpenStandardInput(), process.StandardInput.BaseStream);
        }
    }
}

My child app's code looks like:

using System;

namespace ChildConsoleApp
{
    internal static class Program
    {
        public static void Main()
        {
            Console.WriteLine("Hi");

            string text; // Echo all input
            while ((text = Console.ReadLine()) != "stop")
                Console.WriteLine("Echo: " + text);

            Console.WriteLine("Stopped.");
        }
    }
}

As always this has some overhead, though it's likely insignificant in any non-trivial application you're wrapping around.

If you're sending a lot of data, and want to avoid flushing more than necessary, you can increase the buffer size from 4KB to whatever suits you.

Aidiakapi
  • 6,034
  • 4
  • 33
  • 62
  • Not sure how that one is different from my solution except you spawn some threads? It still uses the Process.Redirects and calls Process.StandardInput/Output as mine does? What am I missing? – Chris Lee May 29 '15 at 22:44
  • This is the solution that I wanted to avoid with my original question. Your code on its own doesn't really do anything, the question wasn't about how to put input into the hosted application. That was a given, and there's likely plenty more topics about it. It was about how to have the child console fully control the main app's console, while still allowing the host app to communicate through the channels. – Aidiakapi May 29 '15 at 22:58
  • Ah I see, yeah my bad misread the original question. I was looking for a noddy wrapper to abstract some console testing away. – Chris Lee May 31 '15 at 00:26
1

Old question, but just up came up with following to solve a similar problem.

public class ConsoleAppWrapper
{
    private Process _process;

    public ConsoleAppWrapper(string exeFilename)
    {
        // Start the Player console app up with hooks to standard input/output and error output
        _process = new Process()
        {
            StartInfo = new ProcessStartInfo(exeFilename)
            {
                UseShellExecute = false,

                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true
            }
        };
        _process.Start();
    }

    public StreamReader StandardOutput => _process.StandardOutput;
    public StreamReader StandardError => _process.StandardError;
    public StreamWriter StandardInput => _process.StandardInput;

    ~ConsoleAppWrapper()
    {
        // When I used this class to help with some SpecFlow feature tests of a console app I found it wasn't destroying the console window correctly when assertions failed
        // using a destructor ensures the windows is destroyed when the wrapper goes out of scope.
        Kill();
    }

    public void Kill()
    {
        Dispose();
    }
}

using this class you can control interaction without console app like so.

        var c = new ConsoleAppWrapper("App.exe");

        Assert.That(c.StandardOutput.ReadLine(), Is.StringContaining("Enter some data:"));
        c.StandardInput.WriteLine("SOME DATA\n");
        Assert.That(c.StandardOutput.ReadLine(), !Is.StringContaining("Error"));
Chris Lee
  • 158
  • 6
  • If I remember correctly (like you said, an old question), this does not print the `App.exe`s output to the console, because you're basically redirecting it to write to your application. That still causes you to have to poll for the app's output and then manually write it to your app's output. The scenario that I wanted to avoid, and the reason for this question. – Aidiakapi May 27 '15 at 14:30
  • Not sure I quite understand the original question then, then can you use the Console.SetIn()/.SetOut() methods to replace the Reader/Writer used by the Console. Other than that the redirect is the only method I can think of. – Chris Lee May 27 '15 at 19:57
  • The code you posted (after some fixing up, such as the `Kill` method) doesn't really do anything on its own. You don't need to have a `ConsoleAppWrapper`, you can just write to the `Process`' standard input then. However, I decided to post the solution to this question, even though it's the non-optimal solution. It's a shame these streams cannot be hooked up directly. – Aidiakapi May 28 '15 at 21:26
0

Its newer going to be pretty.. But atleast this does not rely on sendkeys.

            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "myLegacyConsoleApp.exe",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    WorkingDirectory = @"C:\Program Files\myAppDirectory\",
                }
            };


            process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
            process.Start();

            process.BeginOutputReadLine();
            // You need to wait a bit.. Im sorry..
            System.Threading.Thread.Sleep(5000);
            process.StandardInput.WriteLine("Welcome to Ûberland");

            process.WaitForExit();
Archlight
  • 2,019
  • 2
  • 21
  • 34
  • Watch out for sendkeys and SendInput.. both are unreliable as they will send the keys to currently focused window. – Archlight Nov 19 '12 at 18:21