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

21 浏览
0 Comments

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

我正在尝试通过PowerShell运行一个程序,等待其退出,然后获取ExitCode,但是我运气不太好。我不想使用-WaitStart-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属性?

0
0 Comments

问题的原因是在使用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文档中没有包含这些信息呢?

0
0 Comments

通过使用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)"
}

尽管这种方法有些奇怪,但作为一种解决方法,它完美地起作用。

0
0 Comments

问题的原因:在使用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参数,因此这个解答对他不适用。

0