Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 wanted it to capture the output of the console window and add it to a text box in the GUI. This seemed like it was simple, all I had to do was to redirect the output stream of the target process.

But, when I do that the console application throws the error:

CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents

The current code I have which causes this error is this:

// This gets 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();
}

// This is (like seen above) an event handler for serverProcess.ErrorDataReceived.
private void ServerProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.Output.WriteLine("\n\nServer Error: " + e.Data + "\n\n");
}

// This is (like seen above) an event handler for serverProcess.OutputDataReceived.
private void ServerProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.Output.WriteLine(e.Data);
}

The above code does work 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. Trying to access the output stream without redirecting it also causes an error saying I have to redirect it first.

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

like image 788
alexkarlin Avatar asked Nov 08 '22 05:11

alexkarlin


1 Answers

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

like image 153
Playdome.io Avatar answered Nov 14 '22 21:11

Playdome.io