Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redirect output from running process (Visual C#)

I have a console that is running and I need to get the output. I cannot use startprocess to start the console as it is spawned separately. I do not have access to the source code, I am simply trying to redirect the output from the console while it is already running.

like image 748
Matt Lynch Avatar asked Mar 04 '23 16:03

Matt Lynch


1 Answers

It turns out that attaching to already running separate process using managed framework is not possible.

However, It is possible to achieve this using Console Api Functions under kernel32.dll.

Edit: The code is Improved for better usability

In order to achieve this we need to use FreeConsole, AttachConsole, ReadConsoleOutputCharacter, GetConsoleScreenBufferInfo and AttachConsole from WinApi

Declarations of static external libraries:

[DllImport("kernel32.dll")]
private extern static IntPtr GetStdHandle(int nStdHandle);

[DllImport("kernel32.dll")]
static extern bool ReadConsoleOutputCharacter(IntPtr hConsoleOutput,
  [Out] StringBuilder lpCharacter, uint nLength, COORD dwReadCoord,
  out uint lpNumberOfCharsRead);

[DllImport("kernel32.dll")]
static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool AttachConsole(int dwProcessId);

[DllImport("kernel32.dll")]
static extern bool GetConsoleScreenBufferInfo(
    IntPtr hConsoleOutput,
    out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo
);

[StructLayout(LayoutKind.Sequential)]
struct COORD
{
    public short X;
    public short Y;
}

[StructLayout(LayoutKind.Sequential)]
struct CONSOLE_SCREEN_BUFFER_INFO
{

    public COORD dwSize;
    public COORD dwCursorPosition;
    public short wAttributes;
    public SMALL_RECT srWindow;
    public COORD dwMaximumWindowSize;

}

[StructLayout(LayoutKind.Sequential)]
struct SMALL_RECT
{

    public short Left;
    public short Top;
    public short Right;
    public short Bottom;

}

const int STD_OUTPUT_HANDLE = -11;
const Int64 INVALID_HANDLE_VALUE = -1;

We first need to free current console handle because we can only Attach to a single Console

private static string ReadALineOfConsoleOutput(IntPtr stdout, ref short currentPosition)
{

    if (stdout.ToInt32() == INVALID_HANDLE_VALUE)
        throw new Win32Exception();

    //Get Console Info
    if (!GetConsoleScreenBufferInfo(stdout, out CONSOLE_SCREEN_BUFFER_INFO outInfo))
        throw new Win32Exception();

    //Gets Console Output Line Size
    short lineSize = outInfo.dwSize.X;

    //Calculates Number of Lines to be read
    uint numberofLinesToRead = (uint)(outInfo.dwCursorPosition.Y - currentPosition);

    if (numberofLinesToRead < 1) return null;

    // read from the first character of the first line (0, 0).
    COORD dwReadCoord;
    dwReadCoord.X = 0;
    dwReadCoord.Y = currentPosition;

    //total characters to be read
    uint nLength = (uint)lineSize * numberofLinesToRead + 2*numberofLinesToRead;

    StringBuilder result = new StringBuilder((int)nLength);
    StringBuilder lpCharacter = new StringBuilder(lineSize);
    for (int i = 0; i < numberofLinesToRead; i++)
    {
        if (!ReadConsoleOutputCharacter(stdout, lpCharacter, (uint) lineSize, dwReadCoord, out uint lpNumberOfCharsRead))
            throw new Win32Exception();
        result.AppendLine(lpCharacter.ToString(0, (int)lpNumberOfCharsRead-1));
        dwReadCoord.Y++;
    }

    currentPosition = outInfo.dwCursorPosition.Y;
    return result.ToString();
}

public static async Task Main()
{
    var processId = 8560;
    if (!FreeConsole()) return ;
    if (!AttachConsole(processId)) return;

    IntPtr stdout = GetStdHandle(STD_OUTPUT_HANDLE);
    short currentPosition = 0;

    while (true)
    {
        var r1 = ReadALineOfConsoleOutput(stdout, ref currentPosition);
        if (r1 != null)
            //write to file or somewhere => //Debug.WriteLine(r1);
    }
}

Improvements

  • ref short currentPosition added to ReadALineOfConsoleOutput function for synchronizing currentPosition of the Standard Output
  • GetConsoleScreenBufferInfo is used to get lineSize of the console
    • short lineSize = outInfo.dwSize.X is added for lineSize
  • uint numberofLinesToRead = (uint) (outInfo.dwCursorPosition.Y - currentPosition) is used to calculate number of lines to be read using difference between actual position of the console and current position of the cursor.
  • considering lpNumberOfCharsRead to avoid garbage line endings
like image 60
Derviş Kayımbaşıoğlu Avatar answered Mar 15 '23 06:03

Derviş Kayımbaşıoğlu