如何从 cmd.exe 重定向标准输出

How can I redirect the standard output from cmd.exe

这已被证明很难搜索,因为大多数结果都是关于从 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");
}

这是我从上面得到的输出

*** Running "nslookup google.com 8.8.8.8"...

Non-authoritative answer:

Server:  dns.google
Address:  8.8.8.8

Name:    google.com
Addresses:  2607:f8b0:4002:c08::8b
    2607:f8b0:4002:c08::64
    2607:f8b0:4002:c08::65
    2607:f8b0:4002:c08::66
    172.217.10.206

null
null
Exited
0

*** Running "C:\windows\system32\cmd.exe /C nslookup google.com 8.8.8.8"...

null
null
Exited
0

示例中 nslookup 的选择是任意的,我已经尝试了很多其他命令,包括有副作用的命令,所以我可以确保它按预期执行。

我尝试过同步读取,但没有任何变化。

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

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

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

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

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

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

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

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

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