Redirect output from running process (Visual C#)

2020-06-23 06:47发布

问题:

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.

回答1:

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


回答2:

you need to read https://support.microsoft.com/en-us/help/318804/how-to-set-a-windows-hook-in-visual-c-net Specifically the bottom portion

Global hooks are not supported in the .NET Framework Except for the WH_KEYBOARD_LL low-level hook and the WH_MOUSE_LL low-level hook, you cannot implement global hooks in the Microsoft .NET Framework. To install a global hook, a hook must have a native DLL export to inject itself in another process that requires a valid, consistent function to call into. This behavior requires a DLL export. The .NET Framework does not support DLL exports. Managed code has no concept of a consistent value for a function pointer because these function pointers are proxies that are built dynamically. Low-level hook procedures are called on the thread that installed the hook