使用Start-Process和WaitForExit而不是-Wait获取ExitCode


74

我正在尝试从PowerShell运行程序,等待退出,然后获得对ExitCode的访问权限,但运气不佳。我不希望使用-WaitStart-Process,因为我需要一些处理在后台进行。

这是一个简化的测试脚本:

cd "C:\Windows"

# ExitCode is available when using -Wait...
Write-Host "Starting Notepad with -Wait - return code will be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait)
Write-Host "Process finished with return code: " $process.ExitCode

# ExitCode is not available when waiting separately
Write-Host "Starting Notepad without -Wait - return code will NOT be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru)
$process.WaitForExit()
Write-Host "Process exit code should be here: " $process.ExitCode

运行此脚本将导致记事本启动。手动关闭后,将打印退出代码,并且无需使用即可重新启动退出代码-wait。退出时不提供ExitCode:

Starting Notepad with -Wait - return code will be available
Process finished with return code:  0
Starting Notepad without -Wait - return code will NOT be available
Process exit code should be here:

我需要能够在启动程序与等待程序退出之间执行其他处理,因此我无法使用-Wait。我该怎么做,仍然可以从此过程访问.ExitCode属性?

Answers:


45

我认为您可以做两件事...

  1. 手动创建System.Diagnostics.Process对象并绕过Start-Process
  2. 在后台作业中运行可执行文件(仅适用于非交互式进程!)

您可以按照以下方式进行操作:

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "notepad.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
#Do Other Stuff Here....
$p.WaitForExit()
$p.ExitCode

要么

Start-Job -Name DoSomething -ScriptBlock {
    & ping.exe somehost
    Write-Output $LASTEXITCODE
}
#Do other stuff here
Get-Job -Name DoSomething | Wait-Job | Receive-Job

即使该过程突然终止,第一个代码段也更能防弹,并且可以解决问题。
哈桑

我如何从此处开始获取流程的完整输出?
r4d1um

106

这里有两件事要记住。一是添加自-PassThru变量,二是添加自-Wait变量。由于此缺陷,您需要添加等待参数:http : //connect.microsoft.com/PowerShell/feedback/details/520554/start-process-does-not-re-return-exitcode-property

-PassThru [<SwitchParameter>]
    Returns a process object for each process that the cmdlet started. By d
    efault, this cmdlet does not generate any output.

完成此操作后,将回传一个过程对象,您可以查看该对象的ExitCode属性。这是一个例子:

$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait
$process.ExitCode

# This will print 1

如果在不使用-PassThru或的情况下运行它-Wait,则不会打印任何内容。

相同的答案在这里:如何运行Windows安装程序并在PowerShell中获取成功/失败值?


3
值得注意的解决方法:$ p.GetType()。GetField(“ exitCode”,“ NonPublic,Instance”)。GetValue($ p)
David Martin

2
第一个链接被(有效)断开:“ Microsoft Connect已淘汰”
Peter Mortensen

使用-Wait需要注意的一件事是,它使Start-Process等待所有终止的进程退出-而不仅仅是启动它。通常这不是问题,因为在创建子进程的过程中很少见。但是,我遇到了带有msbuild.exe的问题,该问题生成了不退出的子级。无法使用-Wait,因为会导致脚本挂起。
史蒂夫

48

在尝试上述最终建议时,我发现了一个更简单的解决方案。我要做的就是缓存进程句柄。我这样做之后,$ process.ExitCode便可以正常工作。如果我没有缓存进程句柄,则$ process.ExitCode为null。

例:

$proc = Start-Process $msbuild -PassThru
$handle = $proc.Handle # cache proc.Handle
$proc.WaitForExit();

if ($proc.ExitCode -ne 0) {
    Write-Warning "$_ exited with status code $($proc.ExitCode)"
}

3
我想知道如果有人有什么想法我会感激他们,为什么这行得通。
CarlR

8
@CarlR这是.NET Process对象的实现的怪癖。ExitCode属性的实现首先检查该进程是否已经退出。出于某种原因,执行该检查的代码不仅查看HasExited属性,还验证proces对象中是否存在proces句柄,如果不存在,则会引发异常。PowerShell拦截该异常并返回null。
Jakub Berezanski

5
(续)访问Handle属性会使流程对象检索流程句柄并将其存储在内部。将句柄存储在流程对象中后,ExitCode属性将按预期工作。
Jakub Berezanski

2
这很疯狂,但是作为一种解决方法,它确实可以正常工作。奇怪的!
马克·休斯

14

即使我的过程已经完成,“-等待”选项似乎也对我不利。

我尝试了阿德里安的解决方案,它可以工作。但是我使用Wait-Process而不是依赖于检索处理句柄的副作用。

所以:

$proc = Start-Process $msbuild -PassThru
Wait-Process -InputObject $proc

if ($proc.ExitCode -ne 0) {
    Write-Warning "$_ exited with status code $($proc.ExitCode)"
}

在我看来,使用$proc.HasExited也不可靠。我有几种情况,当在while循环中使用该值时,它会间歇地卡住。我不知道,Wait-Process但这似乎做得更好。谢谢@blt
Manfred

这是最好的答案!在我看来,使用WaitForExit或-Wait也应该起作用,但是此答案是最佳选择。比使用System.Diagnostics.Process简单得多。
史蒂夫

5

或者尝试添加此...

$code = @"
[DllImport("kernel32.dll")]
public static extern int GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);
"@
$type = Add-Type -MemberDefinition $code -Name "Win32" -Namespace Win32 -PassThru
[Int32]$exitCode = 0
$type::GetExitCodeProcess($process.Handle, [ref]$exitCode)

通过使用此代码,您仍然可以让PowerShell负责管理重定向的输出/错误流,而直接使用System.Diagnostics.Process.Start()则无法做到。


2
提示:使用($process.Handle)启动进程后,缓存进程句柄Start-Process。进程退出后将不再设置。
Heinrich Ulbricht

0

这是该主题的一种变体。我想卸载Cisco Amp,等待并获取退出代码。但是卸载程序会启动另一个名为“ un_a”的程序并退出。使用此代码,我可以等待un_a完成并获取它的退出代码,“需要重启”的退出代码为3010。这实际上是在.bat文件中。

如果您曾经想卸载folding @ home,它的工作方式也与此类似。

rem uninstall cisco amp, probably needs a reboot after

rem runs Un_A.exe and exits

rem start /wait isn't useful
"c:\program files\Cisco\AMP\6.2.19\uninstall.exe" /S

powershell while (! ($proc = get-process Un_A -ea 0)) { sleep 1 }; $handle = $proc.handle; 'waiting'; wait-process Un_A; exit $proc.exitcode
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.