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

我正在尝试从 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。退出时不提供退出代码:

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 属性吗?

170905 次浏览

Two things you could do I think...

  1. Create the System.Diagnostics.Process object manually and bypass Start-Process
  2. Run the executable in a background job (only for non-interactive processes!)

Here's how you could do either:

$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

OR

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

Or try adding this...

$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)

By using this code, you can still let PowerShell take care of managing redirected output/error streams, which you cannot do using System.Diagnostics.Process.Start() directly.

There are two things to remember here. One is to add the -PassThru argument and two is to add the -Wait argument. You need to add the wait argument because of this defect.

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

Once you do this a process object is passed back and you can look at the ExitCode property of that object. Here is an example:

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


# This will print 1

If you run it without -PassThru or -Wait, it will print out nothing.

The same answer is here: How do I run a Windows installer and get a succeed/fail value in PowerShell?

It's also worth noting that there's a workaround mentioned in the "defect report" link above, which is as following:

# Start the process with the -PassThru command to be able to access it later
$process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru


# This will print out False/True depending on if the process has ended yet or not
# Needs to be called for the command below to work correctly
$process.HasExited


# This will print out the actual exit code of the process
$process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)

While trying out the final suggestion above, I discovered an even simpler solution. All I had to do was cache the process handle. As soon as I did that, $process.ExitCode worked correctly. If I didn't cache the process handle, $process.ExitCode was null.

example:

$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)"
}

The '-Wait' option seemed to block for me even though my process had finished.

I tried Adrian's solution and it works. But I used Wait-Process instead of relying on a side effect of retrieving the process handle.

So:

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


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

Here's a variation on this theme. I want to uninstall Cisco Amp, wait, and get the exit code. But the uninstall program starts a second program called "un_a" and exits. With this code, I can wait for un_a to finish and get the exit code of it, which is 3010 for "needs reboot". This is actually inside a .bat file.

If you've ever wanted to uninstall folding@home, it works in a similar way.

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