Powershell - 输入stdin捕获stdout / stderr后不再有效

时间:2017-03-01 16:20:02

标签: powershell process stdout stderr plink

在一个非常有限的环境中,如果我想自动执行某些任务,我基本上只允许使用Powershell + Plink。

我想创建一个函数:

  • 显示输出,如果需要,
  • 捕获所有输出(stdout和stderr)以使其稍后可用于进一步解析或记录到console / file / whatever
  • 自动输入密码

不幸的是,在我输入密码输出捕获的行停止之后。它曾经在我只捕获标准输出时工作。在捕获了stderr后,再也没有运气了。

代码:

function BaseRun {
    param ($command, $arguments, $output = "Console")

    $procInfo = New-Object System.Diagnostics.ProcessStartInfo
    $procInfo.RedirectStandardOutput = $true
    $procInfo.RedirectStandardError = $true
    $procInfo.RedirectStandardInput = $true
    $procInfo.FileName = $command
    $procInfo.Arguments = $arguments
    $procInfo.UseShellExecute = $false

    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $procInfo
    [void]$process.Start()

    $outputStream = $process.StandardOutput
    $errorStream = $process.StandardError
    $inputStream = $process.StandardInput

    $outputBuffer = New-Object System.Text.StringBuilder

    Start-Sleep -m 2000
    $inputStream.Write("${env:password}`n")

    while (-not $process.HasExited) {
        do {
            $outputLine = $outputStream.ReadLine()
            $errorLine = $errorStream.ReadLine()
            [void]$outputBuffer.Append("$outputLine`n")

            if (($output -eq "All") -or ($output -eq "Console")) {
                Write-Host "$outputLine"
                Write-Host "$errorLine"
            }
        } while (($outputLine -ne $null) -and ($errorLine -ne $null))
    }

    return $outputBuffer.ToString()
}

2 个答案:

答案 0 :(得分:2)

基于@ w0xx0m和@Martin Prikryl的帮助,我设法制作了这个有效的解决方案:

Register-ObjectEvent -InputObject $process `
    -EventName OutputDataReceived -SourceIdentifier processOutputDataReceived `
    -Action {
    $data = $EventArgs.data
    if($data -ne $null) { Write-Host $data }
} | Out-Null

Register-ObjectEvent -InputObject $process `
    -EventName ErrorDataReceived -SourceIdentifier processErrorDataReceived `
    -Action {
    $data = $EventArgs.data
    if($data -ne $null) { Write-Host $data }
} | Out-Null

[void]$process.Start()
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()

$inputStream = $process.StandardInput

Start-Sleep -m 2000
$inputStream.Write("${env:password}`n")

$process.WaitForExit()

Unregister-Event -SourceIdentifier processOutputDataReceived
Unregister-Event -SourceIdentifier processErrorDataReceived

$inputStream.Close()    

为简洁起见,我已删除了流程启动部分(它与上面的部分相同)和数据处理(在示例中我只是将其写入主机)。

答案 1 :(得分:1)

当你同时阅读stdout和stderr时,你不能使用ReadLine

  • 首先,如果只有stderr输出且没有stdout,$outputStream.ReadLine()永远不会返回,你永远不会进入$errorStream.ReadLine()

  • 更大的问题是,Windows中的输出只有有限的缓冲区。因此,当在生成完整的stdout行之前有很多stderr时,stderr缓冲区会填满并且下一次尝试写入stderr时应用程序(Plink)会停止,等待stderr缓冲区被消耗。它永远不会做什么,因为你一直在等待错误的stdout缓冲区。陷入僵局。

当没有可用的输出时,您必须使用简单的Read并且永远不要同步等待。