Powershell - 输入标准输入后捕获 stdout/stderr 不再有效
Powershell - after inputting to stdin capturing stdout/stderr no longer works
在一个非常受限的环境中,如果我想自动执行某些任务,我基本上只能使用 Powershell + Plink。
我想创建一个函数:
- 如果需要,显示到达的输出
- 捕获所有输出(stdout 和 stderr),以便稍后可用于进一步解析或记录到 console/file/whatever
- 自动输入密码
不幸的是,在我输入密码的那一行输出捕获停止之后。当我只捕获 stdout 时它曾经工作过。也捕获了 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()
}
同时读取 stdout 和 stderr 时,不能不使用 ReadLine
。
首先,如果只有 stderr 输出而没有 stdout,$outputStream.ReadLine()
永远不会 returns 并且您永远不会到达 $errorStream.ReadLine()
.
一个更大的问题是,Windows 中只有有限的缓冲区用于输出。因此,当在生成完整的 stdout 行之前有很多 stderr 时,stderr 缓冲区会填满并且应用程序 (Plink) 在下一次尝试写入 stderr 时停止,等待 stderr 缓冲区被消耗。它永远不会做的事情,因为您一直在等待错误的标准输出缓冲区。死锁。
您必须使用简单的 Read
并且在没有可用输出时永远不要同步等待。
基于@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()
为简洁起见,我删除了进程启动部分(它与上面的部分相同)和数据处理部分(在示例中我只是写入托管它)。
在一个非常受限的环境中,如果我想自动执行某些任务,我基本上只能使用 Powershell + Plink。
我想创建一个函数:
- 如果需要,显示到达的输出
- 捕获所有输出(stdout 和 stderr),以便稍后可用于进一步解析或记录到 console/file/whatever
- 自动输入密码
不幸的是,在我输入密码的那一行输出捕获停止之后。当我只捕获 stdout 时它曾经工作过。也捕获了 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()
}
同时读取 stdout 和 stderr 时,不能不使用 ReadLine
。
首先,如果只有 stderr 输出而没有 stdout,
$outputStream.ReadLine()
永远不会 returns 并且您永远不会到达$errorStream.ReadLine()
.一个更大的问题是,Windows 中只有有限的缓冲区用于输出。因此,当在生成完整的 stdout 行之前有很多 stderr 时,stderr 缓冲区会填满并且应用程序 (Plink) 在下一次尝试写入 stderr 时停止,等待 stderr 缓冲区被消耗。它永远不会做的事情,因为您一直在等待错误的标准输出缓冲区。死锁。
您必须使用简单的 Read
并且在没有可用输出时永远不要同步等待。
基于@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()
为简洁起见,我删除了进程启动部分(它与上面的部分相同)和数据处理部分(在示例中我只是写入托管它)。