使用Start-Process和WaitForExit获取ExitCode而不是使用-Wait。
使用Start-Process和WaitForExit获取ExitCode而不是使用-Wait。
我正在尝试通过PowerShell运行一个程序,等待其退出,然后获取ExitCode,但是我运气不太好。我不想使用-Wait
和Start-Process
,因为我需要一些后台进行的处理。\n这是一个简化的测试脚本:\n
cd "C:\Windows" # 使用-Wait时,可以使用ExitCode... Write-Host "使用-Wait启动记事本-返回代码将可用" $process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait) Write-Host "进程完成,返回代码为:" $process.ExitCode # 在单独等待时,无法使用ExitCode Write-Host "不使用-Wait启动记事本-返回代码将不可用" $process = (Start-Process -FilePath "notepad.exe" -PassThru) $process.WaitForExit() Write-Host "进程退出代码应在这里:" $process.ExitCode
\n运行此脚本将启动记事本。在手动关闭后,将打印退出代码,并且它将再次启动,而不使用-wait
。当退出时,不提供ExitCode:\n
使用-Wait启动记事本-返回代码将可用 进程完成,返回代码为:0 不使用-Wait启动记事本-返回代码将不可用 进程退出代码应在这里:
\n我需要能够在启动程序和等待其退出之间执行其他处理,所以我不能使用-Wait
。我如何做到这一点,同时仍然可以访问此进程的.ExitCode属性?
问题的原因是在使用Start-Process命令和-Wait参数时无法获取进程的退出代码。解决方法是手动创建System.Diagnostics.Process对象并绕过Start-Process命令,或者在后台作业中运行可执行文件(仅适用于非交互式进程)。下面是两种解决方法的示例代码:
第一种方法:
$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 # 在这里进行其他操作... $p.WaitForExit() $p.ExitCode
第二种方法:
Start-Job -Name DoSomething -ScriptBlock { & ping.exe somehost Write-Output $LASTEXITCODE } # 在这里进行其他操作... Get-Job -Name DoSomething | Wait-Job | Receive-Job
第一个代码段更加可靠,即使进程意外终止也能正常工作。如果想要获取进程的完整输出,可以使用第二个代码段。关于Start-Process命令创建了System.Diagnostics.Process对象的信息,需要到这里才能学到。为什么PowerShell文档中没有包含这些信息呢?
通过使用Start-Process和WaitForExit而不是-Wait来获取ExitCode的原因是因为这是.NET Process对象实现的一个怪异之处。ExitCode属性的实现首先检查进程是否已退出。由于某种原因,执行这个检查的代码不仅查看HasExited属性,还验证进程对象中是否存在进程句柄,并在不存在时抛出异常。PowerShell拦截该异常并返回null。
访问Handle属性会导致进程对象检索进程句柄并在内部存储它。一旦句柄存储在进程对象中,ExitCode属性就能按预期工作。
因此,通过缓存进程句柄,即使不使用-Wait参数,也能正确获取ExitCode。以下是一个示例代码:
$proc = Start-Process $msbuild -PassThru $handle = $proc.Handle # 缓存进程句柄 $proc.WaitForExit(); if ($proc.ExitCode -ne 0) { Write-Warning "$_ exited with status code $($proc.ExitCode)" }
尽管这种方法有些奇怪,但作为一种解决方法,它完美地起作用。
问题的原因:在使用Start-Process和WaitForExit获取ExitCode时,如果不添加-PassThru和-Wait参数,将无法获得ExitCode属性。
解决方法:添加-PassThru参数和-Wait参数。添加-Wait参数是因为存在一个缺陷,需要等待进程结束才能获取ExitCode。添加-PassThru参数后,将返回一个进程对象,可以通过该对象的ExitCode属性获取ExitCode的值。
下面是一个示例:
$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait $process.ExitCode # 将打印出1
如果不添加-PassThru或-Wait参数,将不会有任何输出。
在上述解决方法中,还提到了一个缺陷报告中的解决方法:
# 使用-PassThru参数启动进程,以便稍后访问它 $process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru # 根据进程是否已结束打印False/True # 需要调用下面的命令才能正确工作 $process.HasExited # 打印出进程的实际退出码 $process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)
还值得注意的是,在“缺陷报告”链接中提到了一个解决方法:
$p.GetType().GetField("exitCode", "NonPublic,Instance").GetValue($p)
使用-Wait参数时需要注意,它会导致Start-Process等待所有子进程退出,而不仅仅是它自己启动的进程。通常情况下,这不是一个问题,因为进程很少创建子进程。但是,对于会生成不退出的子进程的进程,比如msbuild.exe,就无法使用-Wait参数,因为会导致脚本挂起。
问题提问者明确表示不能使用-Wait参数,因此这个解答对他不适用。