如何从 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 任务对调用调用的访问权限有限。
这已被证明很难搜索,因为大多数结果都是关于从 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 任务对调用调用的访问权限有限。