Powershell:系统诊断-processstartinfo 挂起,原因未知

问题描述 投票:0回答:2

深受 Stackoverflow 上其他问题的影响,我最终采用了这种从 Powershell 脚本启动进程的方法

function global:system-diagnostics-processstartinfo {
[CmdletBinding(SupportsShouldProcess=$True,ConfirmImpact='Low')]
param
(
    [Parameter(Mandatory=$True,HelpMessage='Full path to exectuable')]
    [Alias('exectuable')]
    [string]$exe,
    [Parameter(Mandatory=$True,HelpMessage='All arguments to be sent to exectuable')]
    [Alias('args')]
    [string]$arguments
)

if (!(Test-Path $exe)) {
    $log.errorFormat("Did not find exectuable={0}, aborting script", $exe)
    exit 1  
}

$log.infoFormat("Start exectuable={0} with arguments='{1}'", $exe, $arguments)
$processStartInfo = New-Object System.Diagnostics.ProcessStartInfo($exe)
$processStartInfo.FileName = $exe
$processStartInfo.RedirectStandardError = $true
$processStartInfo.RedirectStandardOutput = $true
$processStartInfo.UseShellExecute = $false
$processStartInfo.Arguments = $arguments
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $processStartInfo
$log.info("Start exectuable and wait for exit")
$p.Start() | Out-Null

#$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$log.infoFormat("exectuable={0}  stdout: {1}", $exe, $stdout)
$log.debugFormat("exectuable={0} stderr: {1}",  $exe,$stderr)
$global:ExitCode = $p.ExitCode
$log.debugFormat("exectuable={0} Exitcode: {1}", $exe, $p.ExitCode)

return $stdout
}

非常简单,添加了一些日志记录等。它适用于我当前的所有用例,除了一个。我创建了一个脚本,将 Confluence 生产实例的数据库转储复制到测试服务器。然后它使用上面的方法删除现有数据库,一切正常。但实际的恢复只是永远挂起。所以现在我必须退出脚本,然后手动运行以下命令

d:\postgresql\bin\pg_restore.exe -U postgres -d confluencedb -v -1 d:\temp\latest-backup.pgdump

需要一些时间,产出也相当多。这让我相信一定有以下任一原因导致了问题

  • 输出量导致缓冲区溢出并导致脚本停止
  • 需要很多时间

有类似经历的人可以帮我解决这个问题。它将能够安排导入,而不必像今天一样手动执行。

powershell processstartinfo
2个回答
0
投票

处理后我必须立即执行以下操作。开始:

# Capture output during process execution so we don't hang
# if there is too much output.
do
{
    if (!$process.StandardOutput.EndOfStream)
    {
        [void]$StdOut.AppendLine($process.StandardOutput.ReadLine())
    }
    if (!$process.StandardError.EndOfStream)
    {
        [void]$StdErr.AppendLine($process.StandardError.ReadLine())
    }

    Start-Sleep -Milliseconds 10
}
while (!$process.HasExited)
        
# Capture any standard output generated between our last poll and process end.
while (!$process.StandardOutput.EndOfStream)
{
    [void]$StdOut.AppendLine($process.StandardOutput.ReadLine())
}
    
# Capture any error output generated between our last poll and process end.
while (!$process.StandardError.EndOfStream)
{
    [void]$StdErr.AppendLine($process.StandardError.ReadLine())
}

# Wait for the process to exit.
$process.WaitForExit()
LogWriteFunc ("END process: " + $ProcessName)

if ($process.ExitCode -ne 0)
{
    LogWriteFunc ("Error: Script execution failed: " + $process.ExitCode )
    $FuncResult = 1
}

# Log and display any standard output.
if ($StdOut.Length -gt 0)
{
    LogWriteFunc ($StdOut.ToString())
}
    
# Log and display any error output.
if ($StdErr.Length -gt 0)
{
    LogWriteFunc ($StdErr.ToString())
}

0
投票

Greg Prosch提供的答案可能会陷入僵局。如此处所述:

如果没有缓冲任何字符,StreamReader.EndOfStream 属性将调用 StandardOutput.Read()。如果进程没有向其输出管道发送任何内容并且没有关闭它,则 Read() 调用将阻塞。

此代码采用了

Microsoft 文档 关于防止死锁的建议,以及 这篇文章 关于确保捕获所有输出的建议:

$global:stdErr = [System.Text.StringBuilder]::new() $global:myprocessrunning = $true Function Invoke-CustomCommand { Param ( $commandPath, $commandArguments, $workingDir = (Get-Location), $path = @() ) $global:stdErr.Clear() $global:myprocessrunning = $true $path += $env:PATH $newPath = $path -join [IO.Path]::PathSeparator $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = $commandPath $pinfo.WorkingDirectory = $workingDir $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8 $pinfo.StandardErrorEncoding = [System.Text.Encoding]::UTF8 $pinfo.UseShellExecute = $false $pinfo.Arguments = $commandArguments $pinfo.EnvironmentVariables["PATH"] = $newPath $p = New-Object System.Diagnostics.Process # https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardoutput # Reading from one stream must be async # We read the error stream, because events can be handled out of order, # and it is better to have this happen with debug output Register-ObjectEvent -InputObject $p -EventName "ErrorDataReceived" -Action { $global:stdErr.AppendLine($EventArgs.Data) } | Out-Null # We must wait for the Exited event rather than WaitForExit() # because WaitForExit() can result in events being missed # https://stackoverflow.com/questions/13113624/captured-output-of-command-run-by-powershell-is-sometimes-incomplete Register-ObjectEvent -InputObject $p -EventName "Exited" -action { $global:myprocessrunning = $false } | Out-Null $p.StartInfo = $pinfo $p.Start() | Out-Null $p.BeginErrorReadLine() # Wait 10 minutes before forcibly killing the process $processTimeout = 1000 * 60 * 10 while (($global:myprocessrunning -eq $true) -and ($processTimeout -gt 0)) { # We must use lots of shorts sleeps rather than a single long one otherwise events are not processed $processTimeout -= 50 Start-Sleep -m 50 } $output = $p.StandardOutput.ReadToEnd() if ($processTimeout -le 0) { $p.Kill() } $executionResults = [pscustomobject]@{ StdOut = $output StdErr = $global:stdErr.ToString() ExitCode = $p.ExitCode } return $executionResults }
    
© www.soinside.com 2019 - 2024. All rights reserved.