重定向N级子进程的标准输出

时间:2014-10-05 03:47:20

标签: c# winapi console-application redirectstandardoutput

我在创建单个进程时成功重定向输出。但是,当THAT进程产生其他进程时,不会重定向输出。除了创建初始过程之外,我无法控制如何/何时实例化孙子进程。似乎唯一的其他选项可能是Win32 API调用,但我没有找到这种方法的证据。

1 个答案:

答案 0 :(得分:5)

当我们无法控制产生子进程时,我担心没有简单的方法可以实现这一目标。这里有两种方式:

中间的1人:我们用我们自己的IO重定向程序可执行文件替换子可执行文件,然后它将启动真正的子进程并将其IO重定向到我们自己的进程。

2-Hooking CreateProcess或Std:我们可以拦截进程创建并强制重定向所有中间进程或拦截Std调用并将它们报告给root。

注意:请注意,这些方法都不会100%有效,它们非常依赖于父和子可执行行为。

我选择中间人,因为它更易于实施且不那么脆弱。

假设我们有3个可执行文件:

PA:我们的root可执行文件,我们可以完全控制代码和行为。

PB:我们的直接子可执行文件,我们可以轻松地将IO重定向到我们自己的进程,但我们无法控制代码和行为。

PC: PB 中产生的间接子产品,我们无法控制它,我们无法正常重定向IO,因为它是由创建的PB 过程。

process

我们在这里可以做的是引入另一个 PI 进程 PC ,并将其IO重定向到自身,然后将其发送到我们的根进程(示例中 PA

process 2

这是我们的4个可执行文件的代码

为实现这一目标,我们有4个可执行文件(PA,PB,PC和PI)以及一个库,其中包含用于 PA PI 之间的流程重定向和通信渠道的共享代码我在样本中使用NetMQ,因为它易于使用且可靠,但可以用类似的渠道替换,我们通过Nuget PM> Install-Package NetMQ获取包。

以下是我们的类库的代码,该代码由 PA PI 引用:

public class RedirectProcess
{
    public static Process StartRedirected(string filename, string args = "")
    {
        var process = Process.Start(new ProcessStartInfo
        {
            Arguments = args,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            FileName = filename
        });

        process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
        process.BeginOutputReadLine();

        return process;
    }

    public static Process StartGrab(string filename, Action<string> dataHandle, string args = "")
    {
        var process = Process.Start(new ProcessStartInfo
        {
            Arguments = args,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            FileName = filename
        });

        process.OutputDataReceived += (s, e) => dataHandle(e.Data);
        process.BeginOutputReadLine();

        return process;
    }
}


public class RedirectClient:IDisposable
{
    private readonly NetMQContext _context;
    private readonly RequestSocket _socket;

    public int Id { get; set; }

    public RedirectClient()
    {
        _context = NetMQContext.Create();
        _socket = _context.CreateRequestSocket();
        _socket.Connect("ipc://redirectCollector");
    }

    public void Send(string data)
    {
        _socket.Send(string.Format("{0},{1}", Id, data));
        _socket.Receive();//ack
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}


public class RediretServer:IDisposable
{
    private bool _start;
    public event EventHandler<ProcessDataArgs> ProcessDataReceived;

    public RediretServer()
    {
        _start = true;

       new Thread(() =>
       {
           using (var context = NetMQContext.Create())
           {
               var socket = context.CreateResponseSocket();
               socket.Bind("ipc://redirectCollector");

               while (_start)
               {
                   var res = socket.ReceiveString(TimeSpan.FromSeconds(1));
                   if (string.IsNullOrEmpty(res))
                       continue;

                   if (ProcessDataReceived != null)
                       ProcessDataReceived(this, new ProcessDataArgs(res));

                   socket.Send(new byte[]{ });//ack
               }
           }
       }).Start();
    }

    public void Dispose()
    {
        _start = false;
    }
}


public class ProcessDataArgs:EventArgs
{
    public string Data { get;private set; }
    public int Id { get;private set; }

    public ProcessDataArgs(string result)
    {
        var i = result.IndexOf(','); //first comma

        Id = int.Parse(result.Substring(0, i));
        Data=result.Substring(i+1);
    }
}

这是我们的可执行文件:

PA: (我们的主要可执行文件,所有IO都应该重定向到它)

internal class Program
{
    private static void Main(string[] args)
    {
        var server = new RediretServer();
        server.ProcessDataReceived += (s, e) => Console.WriteLine("pid:{0}, data:{1}", e.Id, e.Data);

        RedirectProcess.StartGrab("PB.exe", Console.WriteLine);

        while (true)
        {
            Console.WriteLine("Process A, ID: {0},- Hello", Process.GetCurrentProcess().Id);
            Thread.Sleep(1000);
        }
    }
}

PB: (仅用于模拟情况,我们的直接孩子,我们可以轻松地将其IO重定向到我们自己的)

class Program
{
    static void Main(string[] args)
    {

        var process = Process.Start(new ProcessStartInfo
        {
            Arguments = "-argument",
            FileName = "PC.exe"
        });

        while (true)
        {
            Console.WriteLine("Process B, ID: {0},- Hello", Process.GetCurrentProcess().Id);
            Thread.Sleep(1000);
        }
    }
}

PC: (来自 PB 的目标嵌套子项,我们要重定向其IO)

class Program
{
    static void Main(string[] args)
    {
        var a = args.Length == 0 ? "default" : args[0];

        while (true)
        {
            Console.WriteLine("Process C, ID: {0}, Args: {1}- Hello", Process.GetCurrentProcess().Id, a);
            Thread.Sleep(1000);
        }
    }
}

PI: (我们的重定向程序可执行文件,我们应该将 PC 替换为

class Program
{
    private static void Main(string[] args)
    {
        Console.WriteLine("**Process Redirector for PC**");

        var client = new RedirectClient();

        var interceptArgs = args
            .Aggregate(string.Empty, (current, a) => current + (string.Format("\"{0}\"", a)));

        var process=RedirectProcess.StartGrab("PC-real.exe", s =>
        {
            Console.WriteLine(s);
            client.Send(s);
        },
            interceptArgs);

        client.Id = process.Id;
        process.WaitForExit();

        client.Dispose();
    }
}

}

现在让我们构建它并将所有可执行文件放在一个文件夹中,如果我们运行 PA ,我们应该会看到两个控制台弹出, PA 但不是 PC 控制台输出应该像:

PA输出: (连续流)

  

流程A,ID:85568, - Hello

     

流程B,ID:85640, - Hello

PC输出: (连续流)

  

进程C,ID:87012,Args:-argument- Hello

不要让 PC.exe 重命名为 PC-Real.exe ,并将拦截器 PI.exe 重命名为 PC.exe

如果我们再次运行 PA ,拦截器会弹出而不是原来的 PC ,我们将在 PA 进程中获取其IO。

  

流程A,ID:85144, - Hello

     

流程B,ID:86436, - Hello

     

pid:83660,数据:流程C,ID:83660,Args:-argument- Hello