首页 > 解决方案 > 如何从 cmd.exe 重定向标准输出

问题描述

事实证明,这很难搜索,因为大多数结果都是关于从 WITHIN cmd.exe 重定向而不是 cmd.exe 本身的输出。

我有一个简单的 C# 示例,显示了重定向进程输出并仅打印输出值的工作和非工作测试。

void Main()
{
    // Calling nslookup directly works as expected
    ProcessStartInfo joy = new ProcessStartInfo("nslookup", @"google.com 8.8.8.8");
    // Calling nslookup as a command to cmd.exe does not work as expected
    ProcessStartInfo noJoy = new ProcessStartInfo(Environment.ExpandEnvironmentVariables("%COMSPEC%"), @"/C nslookup google.com 8.8.8.8");

    Console.WriteLine($"*** Running \"{joy.FileName} {joy.Arguments}\"...");
    Console.WriteLine();
    Run(joy);

    Console.WriteLine();
    Console.WriteLine($"*** Running \"{noJoy.FileName} {noJoy.Arguments}\"...");
    Console.WriteLine();
    Run(noJoy);
}

void Run(ProcessStartInfo startInfo)
{
    startInfo.CreateNoWindow = true;
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardError = true;
    startInfo.RedirectStandardOutput = true;

    Process proc = new Process();
    proc.StartInfo = startInfo;
    
    proc.EnableRaisingEvents = true;
    proc.Exited += ReceiveExitNotification;
    proc.ErrorDataReceived += ReceiveStandardErrorData;
    proc.OutputDataReceived += ReceiveStandardOutputData;
    
    proc.Start();
    
    proc.BeginErrorReadLine();
    proc.BeginOutputReadLine();
    
    proc.WaitForExit();
    
    proc.ExitCode.Dump();
}

void ReceiveStandardOutputData(object sender, DataReceivedEventArgs e)
{
    Console.WriteLine(e.Data);
}

void ReceiveStandardErrorData(object sender, DataReceivedEventArgs e)
{
    Console.WriteLine(e.Data);
}

void ReceiveExitNotification(object sender, EventArgs e)
{
    Console.WriteLine("Exited");
}

这是我从上面得到的输出

*** 运行“nslookup google.com 8.8.8.8”...

非权威答案:

服务器:dns.google
地址:8.8.8.8

名称:google.com
地址:2607:f8b0:4002:c08::8b
    2607:f8b0:4002:c08::64
    2607:f8b0:4002:c08::65
    2607:f8b0:4002:c08::66
    172.217.10.206

无效的
无效的
退出
0

*** 运行“C:\windows\system32\cmd.exe /C nslookup google.com 8.8.8.8”...

无效的
无效的
退出
0

示例中 nslookup 的选择是任意的,我尝试了许多其他命令,包括具有副作用的命令,因此我可以确定它按预期执行。

我尝试过同步读取,但没有改变。

我没有理由相信它与 C# 或 .NET 相关。我可以尝试直接 CreateProcess() 测试来确认。

对于上下文,它是一个批处理文件,我实际上希望从中获取输出,这就是需要中间 cmd.exe 进程的原因。

进一步的上下文,它实际上是一个 MSBuild Exec 任务,我试图从中获取输出,所以我对实际调用的控制有限,但我已经看到任务在调试器中运行并将其缩小到这个问题。

标签: cmdio-redirection

解决方案


TLDR;问题中的代码示例在任何普通机器上都可以正常工作。

所以事实证明这是一个权限问题。这是一台公司计算机,我的权限受到限制,但是他们安装了软件,可以授予特定进程的管理权限。cmd.exe 是这些进程之一,因此默认情况下它以管理员身份启动,因此我无法从非提升进程中读取输出流。

一些几乎可以解决该问题的想法:

  • 从 cmd.exe 提示我可以运行set __COMPAT_LAYER=RUNASINVOKER然后运行第二个 cmd.exe,它运行不高,但这并没有真正帮助,因为我仍然无法获得该流。设置 __COMPAT_LAYER 环境变量似乎只影响从 cmd.exe 启动的进程(不是来自 .NET 的 Process.Start() 使用的 CreateProcess() )。

  • RunAs.exe 有一个 /trustlevel 开关,我可以使用它运行未提升的命令,但是我的 Process 对象用于 runas,它不处理任何重定向,甚至在子进程的生命周期内保持打开状态,所以仍然没有好处。

但就我而言,我认为最简单的解决方案是最好的。将 cmd.exe 复制到另一个目录并将其添加到路径的顶部。这修复了提升问题,甚至可以作为我的实际问题的最终解决方案,通过我通过 MSBuild 任务对调用调用的有限访问来处理事件。


推荐阅读