3

I am trying to catch the output from a child console app.

  • When the parent is a console app everything works.
  • When the parent is a windows app then the child fails to run with an exception from Console.ReadKey() saying there is no way to readKey when the stdInput has been redirected. However, I am not redirecting the input (only the output).

What am I missing here?

Child Code (set project to console app):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChildProcess
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            char key = Console.ReadKey().KeyChar;
            Console.Out.WriteLine("stdout");
            Console.Error.WriteLine("stderr");
        }
    }
}

Parent app code: (set project to windows app)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RedirectProcessOutput
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            string fileName = @"ChildPRocess.exe";
            string arg = "i";

            string processOutput = "?";

            Process p = new Process();

            p.StartInfo.UseShellExecute = false;
            p.StartInfo.CreateNoWindow = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = false;

            p.StartInfo.FileName = fileName;
            p.StartInfo.Arguments = arg;
            p.Start();

            processOutput = p.StandardOutput.ReadToEnd();

            p.WaitForExit();

            Console.WriteLine("The child process output is:" + processOutput);
        }
    }
}

I was expecting the application to run without crashing even when the parent is a window app since the child should have its own console and I am not redirecting the input for that. BTW - everything works when:

  • the child is not doing a "ReadKey" or
  • the parent is not redirecting stdout at all
OSH
  • 2,847
  • 3
  • 25
  • 46

1 Answers1

1

Since it works correctly when the parent process is a console application, why not just temporarily turn it into one?

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool FreeConsole();

private static void Main(string[] args)
{
    Process p = new Process();

    p.StartInfo.UseShellExecute = false;
    p.StartInfo.CreateNoWindow = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.RedirectStandardError = false;

    p.StartInfo.FileName = "Child.exe";
    p.StartInfo.Arguments = "i";

    AllocConsole();
    p.Start();
    FreeConsole();

    string processOutput = p.StandardOutput.ReadToEnd();
    p.WaitForExit();

    Debug.WriteLine("The child process output is:" + processOutput);
}

If you use a lock around AllocConsole/Start/FreeConsole, you can even start up multiple child processes, each with its own console window.


This is an older 'solution' that did not work properly.

Take a look at the CreateProcess flags. Not setting CREATE_NO_WINDOW doesn't give you a new console object; you need CREATE_NEW_CONSOLE. [nope, this was wrong; see below]

Unfortunately, that isn't exposed in the .NET API. Process.Start does the right thing if you let it use ShellExecute, but then you can't redirect stdout. I think what you are trying to do is impossible with System.Diagnostics.Process, you will have to P/Invoke CreateProcess.

Just yesterday I wrote my own System.Diagnostics.Process alternative because I needed another feature not provided by the MS implementation - redirect both stdout and stderr to the same stream. Feel free to reuse my code, but keep in mind it didn't get much testing yet.

I've tested your example with my ProcessRunner, and it works as expected if the parent process is a console application - the child gets its own new console window (not reusing the one from the parent as System.Diagnostics.Process does), it can ReadKey() from it, and stdout is redirected to the parent.

But if the parent process is a windows application, this breaks down -- ReadKey() fails; and stderr output also isn't visible. (this is exactly the same behavior as System.Diagnostics.Process, I just didn't realize that stderr also wasn't working. The reason for this is that you can't just redirect one of the streams - as soon as STARTF_USESTDHANDLES is set, all the streams are redirected.

Now I'm not sure why it works if the parent application is a console. Maybe the redirected stdin/stderr handles refer to the current console, not the console that was current when GetStdHandle was called.

But if GetStdHandle is called within a Windows application, it returns INVALID_HANDLE. I'm not sure if there's a way to get a handle that will be valid once a console is created. Answer: do don't that, just create the console earlier (see new solution at beginning).

Daniel
  • 15,944
  • 2
  • 54
  • 60
  • thanks. I tried the PInvoke path, but redirecting the output looks like too much work right now (I have something similar in C++, but I am not inclined to write it again in c#). I decided to change the child process to a window application (so it can do input). It looks kind of ugly (that would be my poor UI skills), but does what I want. – OSH Apr 07 '13 at 17:17
  • @OSH: I found a simple solution by temporarily creating a console for the parent process. Oh and I somehow thought you were actually starting a child process outside of your control, so that you __had__ to use its stdout. If you can change the child process, you could also create a pipe unrelated to stdout/stderr. – Daniel Apr 07 '13 at 17:32