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

1

8

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?

Kanter answered 27/6, 2018 at 15:50 Comment(7)
Sure, you could do it the other way around and use the output from the application as input to yours using piping.Biased
How would that work? Wouldn't it still require redirecting the output of the application?Kanter
@alexkarlin I have this exact problem. Did you ever find a solution?Vuillard
@alexkarlin Also, just curious, were you also making a GUI for a csgo server? :)Vuillard
@Vuillard I did not find any solution. :( I ended up repositioning the command-line window to the center of my GUI, but it wasn't a very elegant solution at all. And yes, almost. I was making a GUI for a Garry's Mod server. :)Kanter
@jesbus I know it's very late but i posted an answer, hopefully still relevant :)Historiography
@Biased GetNumberOfConsoleInputEvents uses console handles and can't work with pipes, undocumented tweak with winapi. See full solution & answerHistoriography
H
4

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

Historiography answered 20/11, 2021 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.