Search code examples
c#windowsoutput-redirectconsole-output

How to read console output of a process without redirecting standard output?


I'm writing a GUI for a third-party console application and I want it to capture the output of a console window and add it to a text box in the GUI. Problem is, when I try to redirect the output stream of the target process, the target process crashes with the following error message:

CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents

The code which triggers this error is the following:

// Called once after the application has initialized.
private void StartServer()
{
    ProcessStartInfo processStartInfo = new ProcessStartInfo();
    processStartInfo.FileName = srcdsExeFile;
    processStartInfo.UseShellExecute = false;
    processStartInfo.CreateNoWindow = true;
    processStartInfo.RedirectStandardOutput = true;
    processStartInfo.RedirectStandardError = true;
    processStartInfo.RedirectStandardInput = true;

    serverProcess = Process.Start(processStartInfo);
    serverProcess.EnableRaisingEvents = true;
    serverProcess.Exited += new EventHandler(Server_Exited);
    serverProcess.OutputDataReceived += ServerProcess_OutputDataReceived;
    serverProcess.ErrorDataReceived += ServerProcess_ErrorDataReceived;
    serverProcess.BeginOutputReadLine();
    serverProcess.BeginErrorReadLine();
}

private void ServerProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.Output.WriteLine("\n\nServer Error: " + e.Data + "\n\n");
}

private void ServerProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.Output.WriteLine(e.Data);
}

The above code works for a minute or so while the external application is doing its initialization, but crashes after a specific point in the initialization process.

After doing some research it turns out that the third-party console application relies on the output stream to be a console, which is why it crashes when I try to redirect it to something that isn't a console. However, trying to access the output stream without redirecting it causes another error saying I have to redirect it first.

Which brings me to my actual question:
Is it possible to read the output of a console application without redirecting the output stream?


Solution

  • So this has been asked several times in the past couple of years.

    I just run into the same issue and solved it in C++ but the same technique should apply to any other programming language since this is a WinAPI specific problem. I've describe a solution for anyone that wishes to create an srcds server using CreateProcess and redirect input & output on windows.

    This github repo put together how Console Handls & Standard Handles work together inside windows. https://github.com/rprichard/win32-console-docs

    Also the microsoft documentation of https://learn.microsoft.com/en-us/windows/console/creation-of-a-console

    I highly recommend to read about this as this makes it very obvious why srcds fails when redirecting the standard input.

    The problem

    a) Windows Console Handle is not equal to StandardInput and Output Handles.

    b) And in windows there is no way to redirect Console handles.

    c) GetNumberOfConsoleInputEvents requires a valid console handle with an input handle that is not a file, pipe. It must be a ConsoleHandle!

    Since no one actually looks at why GetNumberOfConsoleInputEvents fails while it should be obvious after reading the documentation.

    https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents

    It clearly states:

    hConsoleInput [in]

    A handle to the console input buffer. The handle must have the GENERIC_READ access right. For more information, see Console Buffer Security and Access Rights.

    But here https://github.com/rprichard/win32-console-docs#allocconsole-attachconsole-modern it's explained that when you redirect the pipe it pretty much breaks the console input buffer. So you have to actually work with the console input buffer and not with the StdHandles.

    The solution

    Luckily the WinAPI provides several options for us to get access to an existing process's std handles. It's just very tricky and not well documented! You can attach to a Console and grab the STDHandles. Duplicate them and do whatever you like. Note that AttachConsole(ProcessId) requires you that the current process has no console attached so you must call FreeConsole();

    Here's a code how to send a single letter to an another application's console using WinAPI. You can also grab the console handle and write to it any time Using GetStdHandle.

            int ProcessId = GetProcessId(ProcessInfo.hProcess);
            if (ProcessId <= 0)
            {
                printf("Process terminated.\n");
                break;
            }
            printf("Process Id: %d\n", ProcessId);
    
            FreeConsole();
            if (!AttachConsole(ProcessId))
            {
                printf("Attach failed with error: %d\n", GetLastError());
                exit(1);
            }
    
            INPUT_RECORD ir[2];
            ir[0].EventType = KEY_EVENT;
            ir[0].Event.KeyEvent.bKeyDown = TRUE;
            ir[0].Event.KeyEvent.dwControlKeyState = 0;
            ir[0].Event.KeyEvent.uChar.UnicodeChar = 'u';
            ir[0].Event.KeyEvent.wRepeatCount = 1;
            ir[0].Event.KeyEvent.wVirtualKeyCode = 'U';
            ir[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey('U', MAPVK_VK_TO_VSC);
    
            ir[1].EventType = KEY_EVENT;
            ir[1].Event.KeyEvent.bKeyDown = FALSE;
            ir[1].Event.KeyEvent.dwControlKeyState = 0;
            ir[1].Event.KeyEvent.uChar.UnicodeChar = 'u';
            ir[1].Event.KeyEvent.wRepeatCount = 1;
            ir[1].Event.KeyEvent.wVirtualKeyCode = 'U';
            ir[1].Event.KeyEvent.wVirtualScanCode = MapVirtualKey('U', MAPVK_VK_TO_VSC);
    
            DWORD dwTmp = 0;
            WriteConsoleInputA(GetStdHandle(STD_INPUT_HANDLE), ir, 2, &dwTmp);
    
            FreeConsole();
            if (!AttachConsole(ATTACH_PARENT_PROCESS))
            {
                printf("Attach failed with error: %d\n", GetLastError());
                exit(1);
            }
    

    So the solution is simply to to write to the Console Input Buffer by attaching to the console of an SRCDS process. Simply call AttachConsole and FreeConsole. and WriteConsoleInput.

    To read the ouptut you can just call ReadConsoleOutput

    For further reading visit the documentation:

    GetNumberOfConsoleInputEvents

    https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents

    AttachConsole

    https://learn.microsoft.com/en-us/windows/console/attachconsole

    FreeConsole

    https://learn.microsoft.com/en-us/windows/console/freeconsole

    WriteConsoleInput

    https://learn.microsoft.com/en-us/windows/console/writeconsoleinput

    ReadConsoleOutput

    https://learn.microsoft.com/en-us/windows/console/readconsoleoutput