使用启动过程捕获标准输出和错误


111

Start-Process访问StandardErrorStandardOutput属性时,PowerShell 命令中是否存在错误?

如果运行以下命令,则无输出:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.StandardOutput
$process.StandardError

但是,如果我将输出重定向到文件,则会得到预期的结果:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt

5
在这种情况下,您是否真的需要启动流程?... $process= ping localhost #将输出保存在流程变量中。
mjsr 2012年

1
真正。我一直在寻找一种更简单的方法来处理返回值和参数。我最终像您展示的那样编写了脚本。
jzbruno 2012年

Answers:


127

Start-Process是出于某种原因而设计的。这是一种无需发送到文件即可获取的方法:

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode

7
我接受你的回答。我希望他们不会创建未使用的属性,这非常令人困惑。
jzbruno 2012年

6
如果您无法通过这种方式运行进程,请在此处stackoverflow.com/questions/11531068/…上查看已接受的答案,该文件对WaitForExit和StandardOutput.ReadToEnd进行了少许修改
Ralph Willgoss 2012年

3
当您使用-verb runAs时,它不允许-hNewWindow或Redirection Options
Maverick

15
由于StdErr和StdOut被同步读取到最后,因此在某些情况下,此代码将死锁。msdn.microsoft.com/en-us/library/...
codepoke

8
@codepoke-比它稍差一些-因为它首先进行WaitForExit调用,即使它仅重定向了其中一个,如果流缓冲区被填满,它也可能死锁(因为它直到处理过程才会尝试从中读取数据)已退出)
James Manning

20

在问题给出的代码中,我认为读取启动变量的ExitCode属性应该可以工作。

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.ExitCode

请注意(如您的示例所示),您需要添加-PassThru-Wait参数(这使我陷入了一段时间)。


如果argumentslist包含变量怎么办?它似乎没有扩大。
2016年

1
您可以将参数列表放在引号中。那行得通吗?... $ process = Start-Process -FilePath ping -ArgumentList“ -t localhost -n 1” -NoNewWindow -PassThru
-Wait

如何在Powershell窗口中显示输出以及将其记录到日志文件中?可能吗?
Murali Dhar Darshan

不能-NoNewWindow-Verb runAs
Dragas

11

我也遇到了这个问题,最终使用Andy的代码创建了一个函数,以在需要运行多个命令时清理事物。

它将返回stderr,stdout和退出代码作为对象。需要注意的一件事:函数不会.\在路径中接受;必须使用完整路径。

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $commandPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $commandArguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    [pscustomobject]@{
        commandTitle = $commandTitle
        stdout = $p.StandardOutput.ReadToEnd()
        stderr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
    }
}

使用方法如下:

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"

好主意,但似乎语法对我不起作用。参数列表不应该使用param([type] $ ArgumentName)语法吗?您可以为此函数添加示例调用吗?
Lockszmith '16

关于“需要注意的一件事:该函数在路径中将不接受。\;必须使用完整路径。”:您可以使用:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz

8

我确实对Andy ArismendiLPG的例子感到困扰。您应该始终使用:

$stdout = $p.StandardOutput.ReadToEnd()

致电之前

$p.WaitForExit()

一个完整的例子是:

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$p.WaitForExit()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode

您在哪里读到“应该始终在$ p.WaitForExit()之前使用$ p.StandardOutput.ReadToEnd()”?如果缓冲区上的输出已用完,则稍后再输出更多内容,如果执行行在WaitForExit上且进程尚未完成(随后输出更多的stderr或stdout),则会丢失该输出。
CJBS

关于上面的评论,后来我看到了关于在大输出情况下死锁和缓冲区溢出的公认答案的评论,但是除此之外,我希望仅仅是因为缓冲区读到最后,并不意味着该过程已经完成,因此可能会遗漏更多的输出。我想念什么吗?
CJBS

@CJBS:“仅仅因为缓冲区读到末尾,并不意味着该过程已经完成” –确实是这样。实际上,这就是它可能会死锁的原因。阅读“到最后”并不意味着“阅读现在的内容 ”。这意味着开始读取,并且直到流关闭才停止,这与终止过程相同。
Peter Duniho

8

重要:

我们一直在使用LPG上面提供的功能。

但是,它包含一个错误,当您启动一个生成大量输出的过程时,可能会遇到该错误。因此,使用此功能时可能会陷入死锁。而是使用以下改编版本:

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
  Try {
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $commandPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $commandArguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    [pscustomobject]@{
        commandTitle = $commandTitle
        stdout = $p.StandardOutput.ReadToEnd()
        stderr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
    }
    $p.WaitForExit()
  }
  Catch {
     exit
  }
}

可以在MSDN上找到有关此问题的更多信息:

如果父进程在p.StandardError.ReadToEnd之前调用p.WaitForExit,并且子进程写入足够的文本以填充重定向的流,则可能导致死锁。父进程将无限期地等待子进程退出。子进程将无限期等待父进程从完整的StandardError流中读取。


3
由于对ReadToEnd()的同步调用,该代码仍然死锁,您对MSDN的链接也对此进行了描述。
bergmeister

1
现在,这似乎已经解决了我的问题。我必须承认,我不完全理解为什么它会挂起,但是似乎空的stderr阻止了该过程的完成。奇怪的是,由于它已经工作了很长时间,但是突然在Xmas出现之前就开始出现故障,从而导致许多Java进程挂起。
rhellem

0

这是我的函数版本,该函数返回带有3个新属性的标准System.Diagnostics.Process

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
    Try {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $commandPath
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.WindowStyle = 'Hidden'
        $pinfo.CreateNoWindow = $True
        $pinfo.Arguments = $commandArguments
        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $stdout = $p.StandardOutput.ReadToEnd()
        $stderr = $p.StandardError.ReadToEnd()
        $p.WaitForExit()
        $p | Add-Member "commandTitle" $commandTitle
        $p | Add-Member "stdout" $stdout
        $p | Add-Member "stderr" $stderr
    }
    Catch {
    }
    $p
}

0

这是从另一个Powershell进程获取输出的一种怪诞的方法:

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'; import-clixml out.xml
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.