PowerShell中通过psexec.exe调用vmrun.exe无法获取StdOut/StdErr如何解决
问题根因
你遇到的输出丢失问题由3个常见原因导致:
- PsExec调用时缺少必要参数,默认没有完全透传子进程的标准输出/错误
- 你当前使用的
Invoke-Process函数存在输出读取逻辑缺陷,先等待进程退出再同步读取两个输出流,极易触发死锁导致输出被截断/丢失 - 未处理PsExec首次运行的EULA确认逻辑,可能导致进程静默卡住无输出
修复方案
1. 优化Invoke-Process函数
修正输出读取逻辑,改用异步读取避免死锁:
function Invoke-Process { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$FilePath, [Parameter()] [ValidateNotNullOrEmpty()] [string]$ArgumentList, [ValidateSet("Full","StdOut","StdErr","ExitCode","None")] [string]$DisplayLevel ) $ErrorActionPreference = 'Stop' try { $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = $FilePath $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8 $pinfo.StandardErrorEncoding = [System.Text.Encoding]::UTF8 $pinfo.UseShellExecute = $false $pinfo.WindowStyle = 'Hidden' $pinfo.CreateNoWindow = $true $pinfo.Arguments = $ArgumentList $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.EnableRaisingEvents = $true # 异步收集输出避免死锁 $stdOutBuilder = New-Object System.Text.StringBuilder $stdErrBuilder = New-Object System.Text.StringBuilder Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action { param($sender, $e) if (-not [string]::IsNullOrEmpty($e.Data)) { $Event.MessageData.AppendLine($e.Data) } } -MessageData $stdOutBuilder | Out-Null Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action { param($sender, $e) if (-not [string]::IsNullOrEmpty($e.Data)) { $Event.MessageData.AppendLine($e.Data) } } -MessageData $stdErrBuilder | Out-Null $p.Start() | Out-Null $p.BeginOutputReadLine() $p.BeginErrorReadLine() $p.WaitForExit() # 等待异步事件处理完成 Start-Sleep -Milliseconds 100 $result = [pscustomobject]@{ Title = ($MyInvocation.MyCommand).Name Command = $FilePath Arguments = $ArgumentList StdOut = $stdOutBuilder.ToString().Trim() StdErr = $stdErrBuilder.ToString().Trim() ExitCode = $p.ExitCode } if (-not([string]::IsNullOrEmpty($DisplayLevel))) { switch($DisplayLevel) { "Full" { return $result; break } "StdOut" { return $result.StdOut; break } "StdErr" { return $result.StdErr; break } "ExitCode" { return $result.ExitCode; break } } } } catch { Write-Error $_ } }
2. 修正PsExec调用参数
需要补充以下参数确保输出正常透传:
/accepteula:自动接受PsExec的许可协议,避免首次运行静默卡住-nobanner:关闭PsExec自身的版权banner输出,避免干扰子进程输出- 本地执行需要显式指定
\\localhost作为目标主机 - 如果vmrun需要桌面交互权限,补充
-i参数
修正后的调用示例:
write-host "******************************Call vmrun directly******************************" Invoke-Process -FilePath "C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe" -ArgumentList "stop E:\VMs\VM1\vm1.vmx" -DisplayLevel Full write-host "******************************Call vmrun from psexec******************************" Invoke-Process -FilePath "C:\Windows\System32\PsExec64.exe" -ArgumentList "/accepteula -nobanner \\localhost ""C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"" stop E:\VMs\VM1\vm1.vmx" -DisplayLevel Full
额外注意事项
- 运行PowerShell时需要以管理员权限启动,否则PsExec调用本地系统进程会权限不足
- 如果vmrun操作的虚拟机是在当前用户桌面运行的,需要在PsExec参数中补充
-i 1指定当前会话ID,否则vmrun会找不到目标虚拟机导致无输出
内容的提问来源于stack exchange,提问作者funkmaster
相关产品推荐
相关产品推荐

