When run a program in C#, all the messages go to the standard output, but the standard error contains nothing

Viewed 839

My question is different with the one identified. Obviously I have called "BeginErrorReadLine" method (I mark it in the code below).

I want to parse the result produced by Handle


Command line

When run in a command line environment, it will output something like:

> handle64 -p [PID]

 

Nthandle v4.11 - Handle viewer

Copyright (C) 1997-2017 Mark Russinovich

Sysinternals - www.sysinternals.com

 

  10: File     C:\Windows

  1C: File     C:\Windows\SysWOW64

[PID] is any running process ID

The output is seperated.

First 5 lines (include empty lines) go to the standard error, last 2 lines go to the standard output.

So I can strip the header by redirecting:

> handle64 -p [PID] 2>nul

  10: File     C:\Windows

  1C: File     C:\Windows\SysWOW64


Winform application

Then I try to implement this command in a C# winform application:

Stream streamOut, streamErr;

var p = Process.Start(new ProcessStartInfo
{
    FileName = "handle64.exe",
    Arguments = "-p [PID]",
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
});

p.OutputDataReceived += (sender, e) =>
{
    streamOut.Write("Output => " + e.Data);
};

p.ErrorDataReceived += (sender, e) =>
{
    streamErr.Write("Error => " + e.Data);
};

p.BeginOutputReadLine();
p.BeginErrorReadLine(); // !!!
p.WaitForExit();

Then I find everything go to the standard output.


Question

Ok, I can seperate the header and the body by code.

The question is why the program's output behaves different between the 2 environments?

Can I make the result in the winform application behaves like it in the command line?


Update

For Damien's comment, I try to run the program via 'cmd', unfortunately I get the same result:

var p = Process.Start(new ProcessStartInfo
{
    FileName = "cmd",
    Arguments = "/C handle64.exe -p [PID]",
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
});

...

In output window:

Output =>

Output => Nthandle v4.11 - Handle viewer

Output => Copyright (C) 1997-2017 Mark Russinovich

Output => Sysinternals - www.sysinternals.com

Output =>

Output =>   10: File     C:\Windows

Output =>   1C: File     C:\Windows\SysWOW64

Error =>

4 Answers

Not the answer of your question, but just a suggestion to achieve what you are trying do (i.e only get handle information in Winform application):

handle tool has -nobanner switch, you can use that to skip copyright message information.

handle64.exe -pid 11624 -nobanner

This is just a sample to illustrate the problem I alluded to in my comments. It's not a fix since I don't believe there is a trivial way to fix this. I've created Main in my scratch program (called PlayAreaCSCon). If it's called with no parameters, it's acting a way similar to what I suspect Handle64.exe is doing. When called with a parameter, it contains code similar to your own, but it then launches a copy of itself with no parameters:

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace PlayAreaCSCon
{
    class Program
    {
        [DllImport("kernel32.dll")]
        static extern IntPtr GetConsoleWindow();
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.Out.WriteLine("Hello");
                if (GetConsoleWindow() == IntPtr.Zero)
                {
                    Console.Out.WriteLine("No Console window");
                }
                else
                {
                    Console.Error.WriteLine("We have a console window");
                }
            }
            else
            {
                Process p = Process.Start(new ProcessStartInfo
                {
                    FileName = "PlayAreaCSCon.exe",
                    Arguments = "",
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                });

                TextWriter streamOut = Console.Out;
                TextWriter streamErr = Console.Error;
                p.OutputDataReceived += (sender, e) =>
                {
                    streamOut.WriteLine("Output => " + e.Data);
                };

                p.ErrorDataReceived += (sender, e) =>
                {
                    streamErr.WriteLine("Error => " + e.Data);
                };

                p.BeginOutputReadLine();
                p.BeginErrorReadLine(); // !!!
                p.WaitForExit();
            }
        }
    }
}

In a command prompt, I have the following session:

C:\Dev\PlayAreaCSCon\PlayAreaCSCon\bin\Debug>PlayAreaCSCon.exe
Hello
We have a console window

C:\Dev\PlayAreaCSCon\PlayAreaCSCon\bin\Debug>PlayAreaCSCon.exe a
Error =>
Output => Hello
Output => No Console window
Output =>

So even here, if Handle64.exe is calling GetConsoleWindow or any morally equivalent function, it can detect that it's not connected to a console and exhibit different behaviour. The only way you might let it get a console window would be to set CreateNoWindow to false, which I gather you probably wouldn't want to do.

Since Handle64 is closed source it's difficult to confirm that this is the specific check it's performing either. There's no non-trivial fix to this from the calling side.

As stated by Damien: CreateNoWindow = false,

Let it create the window the immediately hide it. We could create it offscreen, but it would still show on the taskbar.

Note: this code may be no better than letting the Window appear and disappear naturally.

At the top of the class add:

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

Then your code becomes:

var p = Process.Start(new ProcessStartInfo
{
    FileName = "cmd",
    Arguments = "/C handle64.exe -p [PID]",
    CreateNoWindow = false,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
});
p.WaitForInputIdle();
IntPtr windowHandle = p.MainWindowHandle;
if(windowHandle == 0) throw new Exception("This did not work");
// use win32 API's to hide window (May still flicker)
ShowWindow(windowHandle,0);
// ...

I am unable to test this, since I am only running Linux at the moment.
If the exception doesn't fire, you may see a flicker of a window, but you should have the proper output.

The other way I know of doing this involves inserting handlers into the Win32 message pump and respond to the specific process telling it what it needs to know to think it has a proper window when it does not. I will not publicly post any code related to this technique. Any errors would cause Windows to become unstable.

I made some changes to your code:

Stream streamOut, streamErr;

var p = Process.Start(new ProcessStartInfo
{
    FileName = "handle64.exe",
    Arguments = "-p [PID]",
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardInput = true, // even if no writing to std::in, still need this
    RedirectStandardError = true,
});

p.OutputDataReceived += (sender, e) =>
{
    streamOut.Write("Output => " + e.Data);
};
p.BeginOutputReadLine();

p.ErrorDataReceived += (sender, e) =>
{
    streamErr.Write("Error => " + e.Data);
};

p.BeginErrorReadLine(); 

p.WaitForExit();
p.StandardInput.Close(); // call this before WaitForExit
p.WaitForExit();
Related