重定向正在运行的进程的输出(Visual C#)

重定向正在运行的进程的输出(Visual C#),c#,output,io-redirection,C#,Output,Io Redirection,我有一个正在运行的控制台,我需要获取输出。我无法使用startprocess启动控制台,因为它是单独生成的。我没有访问源代码的权限,我只是在控制台已经运行时尝试重定向输出。您需要阅读 特别是底部 .NET Framework中不支持全局挂钩 除了WH_键盘和WH_鼠标低级钩子之外,您不能在Microsoft.NET Framework中实现全局钩子若要安装全局钩子,钩子必须具有本机DLL导出,以便将自身注入另一个进程,该进程需要有效、一致的函数来调用。此行为需要DLL导出。.NET Framew

我有一个正在运行的控制台,我需要获取输出。我无法使用
startprocess
启动控制台,因为它是单独生成的。我没有访问源代码的权限,我只是在控制台已经运行时尝试重定向输出。

您需要阅读 特别是底部

.NET Framework中不支持全局挂钩 除了WH_键盘和WH_鼠标低级钩子之外,您不能在Microsoft.NET Framework中实现全局钩子若要安装全局钩子,钩子必须具有本机DLL导出,以便将自身注入另一个进程,该进程需要有效、一致的函数来调用。此行为需要DLL导出。.NET Framework不支持DLL导出。托管代码没有函数指针一致值的概念,因为这些函数指针是动态生成的代理。 在安装钩子的线程上调用低级钩子过程


事实证明,使用托管框架连接到已经运行的独立进程是不可能的

但是,可以使用
kernel32.dll
下的
控制台Api函数实现这一点

编辑:改进代码以提高可用性

为了实现这一点,我们需要使用
FreeConsole
AttachConsole
ReadConsoleOutputCharacter
GetConsoleScreenBufferInfo
AttachConsole
from
WinApi

静态外部库的声明:

[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;
我们首先需要释放当前控制台句柄,因为我们只能附加到单个控制台

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);
    }
}
private静态字符串ReadALineOfConsoleOutput(IntPtr stdout,ref short currentPosition)
{
if(stdout.ToInt32()==无效的\u句柄\u值)
抛出新的Win32Exception();
//获取控制台信息
如果(!GetConsoleScreenBufferInfo(标准输出,输出控制台\屏幕\缓冲区\信息输出))
抛出新的Win32Exception();
//获取控制台输出行大小
短线尺寸=outInfo.dwSize.X;
//计算要读取的行数
uint numberofLinesToRead=(uint)(outInfo.dwCursorPosition.Y-当前位置);
if(numberofLinesToRead<1)返回null;
//从第一行(0,0)的第一个字符读取。
库德·德雷德库德;
dwReadCoord.X=0;
dwReadCoord.Y=当前位置;
//要读取的字符总数
uint NLENGHT=(uint)线宽*numberofLinesToRead+2*numberofLinesToRead;
StringBuilder结果=新的StringBuilder((int)nLength);
StringBuilder lpCharacter=新的StringBuilder(线宽);
对于(int i=0;i//Debug.WriteLine(r1);
}
}
改进

  • ref short currentPosition
    添加到
    ReadALineOfConsoleOutput
    功能中,用于同步标准输出的currentPosition
  • GetConsoleScreenBufferInfo
    用于获取控制台的
    lineSize
    • 为线宽添加了
      short lineSize=outInfo.dwSize.X
  • uint numberofLinesToRead=(uint)(outInfo.dwCursorPosition.Y-currentPosition)
    用于使用控制台的实际位置和光标的当前位置之间的差值计算要读取的行数
  • 考虑
    lpNumberOfCharsRead
    以避免垃圾行结尾

启动过程
不是C#。你没有访问哪个源代码的权限?“你到底试过什么?”汤马斯韦勒在我即将下班的时候,我很快打了这个。我无法使用我的代码启动我尝试重定向的控制台,因为它是从我使用代码启动的程序派生的。我无法访问生成控制台的程序的源代码,我的代码就是这里的答案。鼠标和键盘挂钩与重定向
stdout
?@ThomasWeller将一些东西带到表中,而不仅仅是SA注释。你读过黑体字吗?用户声明“它是单独生成的”。因此,需要一个钩子链接的文章中不包含以下任何一个词:console、stdout、redirect、output。因此,请给出一个答案,说明如何将您的应用程序注入C#中的另一个进程以重定向输出。用户没有启动他们试图重定向输出的进程。他们给了它一个C#标签。为了将您的应用程序注入到另一个应用程序中,您需要一个全局钩子。”此行为需要DLL导出。NET Framework不支持DLL导出。“不管怎样,我在这里完成了……在您注入该进程之后,下一步是什么?”?我没说我知道怎么做。这在Linux中是不可能的(你需要一个调试器或一个系统调用跟踪器),我想知道它会在Windows上实现哪个概念。我真的很兴奋能学到一些新东西。将赏金增加到200。找到你和其他关于这方面的文章,并为此建立了一个小图书馆。如果其他人想在这件事上有一个好的开端,请加入进来。