Powershell cmdlet Start-Process
表现得非常奇怪:
当我启动另一个控制台进程并指定-NoNewWindow
时,ExitCode属性(int
!)为空。
这是一个测试:与cmd
之外的其他内容相同。这个测试是在带有PS5的Win10上,它与Win7和PS5相同:
PS C:\Users\Martin> cmd.exe /Cver
Microsoft Windows [Version 10.0.15063]
PS C:\Users\Martin> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.15063.296
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.15063.296
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
PS C:\Users\Martin> $pNewWindow = Start-Process -FilePath "cmd.exe" -ArgumentList '/C"exit 42"' -PassThru
PS C:\Users\Martin> $pNewWindow.WaitForExit()
PS C:\Users\Martin> $pNoNewWindow.HasExited
True
PS C:\Users\Martin> $pNewWindow.ExitCode
42
PS C:\Users\Martin> $pNoNewWindow = Start-Process -FilePath "cmd.exe" -ArgumentList '/C"exit 42"' -PassThru -NoNewWindow
PS C:\Users\Martin> $pNoNewWindow.WaitForExit()
PS C:\Users\Martin> $pNoNewWindow.HasExited
True
PS C:\Users\Martin> $pNoNewWindow.ExitCode
PS C:\Users\Martin> $pNoNewWindow.ExitCode -eq $null
True
PS C:\Users\Martin> $pNoNewWindow | Get-Member | ? {$_.Name -imatch "exit"}
TypeName: System.Diagnostics.Process
Name MemberType Definition
---- ---------- ----------
Exited Event System.EventHandler Exited(System.Object, System.EventArgs)
WaitForExit Method bool WaitForExit(int milliseconds), void WaitForExit()
ExitCode Property int ExitCode {get;}
ExitTime Property datetime ExitTime {get;}
HasExited Property bool HasExited {get;}
PS C:\Users\Martin>
...所以,该财产就在那里,但它是null
,即使它是int
?
答案 0 :(得分:2)
我们在环境中遇到了类似的问题。 在执行以下命令时:
Start-Process -FilePath C:\Windows\system32\reg.exe -PassThru -Wait -NoNewWindow
或
Start-Process -FilePath C:\Windows\system32\reg.exe -PassThru -Wait -WindowStyle Hidden
如Martin Ba所说,似乎启动进程(reg.exe)在启动进程能够从进程获取句柄之前已经停止。
使用-Wait
或.WaitForExit()
并没有什么不同,因为这两种方法都检查进程是否已退出。如果退出,PowerShell将无法获取该流程的必要句柄。该句柄是必需的,因为它保存了已启动进程的ExitCode。
如果进程已经退出,则会引发以下错误:
System.Management.Automation.CmdletInvocationException: Cannot process request because the process (<ProcessIdHere>) has exited. --->
System.InvalidOperationException: Cannot process request because the process (<ProcessIdHere>) has exited.
at System.Diagnostics.Process.GetProcessHandle(Int32 access, Boolean throwIfExited)
at System.Diagnostics.Process.OpenProcessHandle(Int32 access)
at System.Diagnostics.Process.get_Handle()
使用-WindowStyle Hidden
代替-NoNewWindow
可以减少发生错误的频率。这是因为-WindowStyle Hidden
正在创建新的Shell(->更多开销),而-NoNewWindow
使用当前的Shell。
我们为此问题创建了一个UserVoice条目。也许PowerShell团队可以解决此问题:https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/35737357--bug-start-process-might-not-return-handle-exitco
答案 1 :(得分:1)
回答linked question的回答/评论:
不得不做的是缓存进程句柄。一旦我这样做,$ process.ExitCode就能正常工作。如果我没有缓存进程句柄,$ process.ExitCode为null。
实际上,对于 不 立即终止的进程(与示例中的cmd.exe不同),解决方法有效:
$proc = Start-Process -NoNewWindow -PassThru ...
$handle = $proc.Handle # cache proc.Handle https://stackoverflow.com/a/23797762/1479211
$proc.WaitForExit()
$proc.ExitCode ... will be set
用户在comments中添加了解释:
这是.NET Process对象实现的一个怪癖。 ExitCode属性的实现首先检查进程是否已退出。出于某种原因,执行该检查的代码不仅查看HasExited属性,还验证过程对象和throws an exception if it is not中是否存在过程句柄。 PowerShell拦截该异常并返回null。
访问Handle属性会导致进程对象检索进程句柄store it internally。句柄存储在进程对象中后,ExitCode属性按预期工作。
由于Start-Process
无法启动暂停的进程,并且只有进程正在运行(看起来)才能获取句柄,因此对于短时间运行的进程来说,使用情况有点脆弱。