【问题标题】:Powershell - after inputting to stdin capturing stdout/stderr no longer worksPowershell - 输入标准输入后捕获标准输出/标准错误不再有效
【发布时间】:2017-07-21 01:56:40
【问题描述】:

在相当受限的环境中,如果我想自动执行某些任务,我基本上只能使用 Powershell + Plink。

我想创建一个函数:

  • 如果需要,在输出到达时显示输出
  • 捕获所有输出(stdout 和 stderr),以便以后进一步解析或记录到控制台/文件/任何内容
  • 自动输入密码

不幸的是,在我输入密码输出捕获停止的行之后。当我只捕获标准输出时,它曾经工作过。在捕获了 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()
}

【问题讨论】:

  • 我没有现成的解决方案,但 IIRC 问题是由您同步执行的事实引起的。所以我认为你需要将$inputStream.Write 移动到一个新线程/RunSpace/whatever 中。或者通过Register-ObjectEvent $Process OutputDataReceived ......进行输出收集
  • 该死,我希望我能避免整个异步shebang。

标签: powershell process stdout stderr plink


【解决方案1】:

在@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()    

为简洁起见,我删除了进程启动部分(与上面的相同)和数据处理(在示例中我只是写托管它)。

【讨论】:

    【解决方案2】:

    当您同时读取标准输出和标准错误时,您不能不使用ReadLine

    • 首先,如果只有 stderr 输出而没有 stdout,则 $outputStream.ReadLine() 永远不会返回,您也永远不会到达 $errorStream.ReadLine()

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

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

    【讨论】:

    • 所以,基本上,我必须开始在任何地方使用 Read()。当输出为空时,我之前使用的代码会中断。但如果我这样做,我认为循环将跳过读取标准错误。
    • 是的。我认为您不需要这两个嵌套循环。只需保留外部while (-not $process.HasExited)
    猜你喜欢
    • 2013-05-11
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 1970-01-01
    • 2013-09-18
    • 1970-01-01
    • 2013-01-11
    • 2011-11-30
    相关资源
    最近更新 更多