Search code examples
powershellprocessstdoutstderrplink

Powershell - after inputting to stdin capturing stdout/stderr no longer works


In a pretty restricted environment I'm basically only allowed to use Powershell + Plink if I want to automate some tasks.

I want to create a function that:

  • shows the output as it arrives if required
  • captures all output (stdout and stderr) to make it available later for further parsing or logging to console/file/whatever
  • inputs a password automatically

Unfortunately after the line where I input the password output capture stops. It used to work when I was only capturing stdout. After capturing stderr as well, no more luck.

The code:

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()
}

Solution

  • Based on @w0xx0m's and @Martin Prikryl's help I managed to make this working solution:

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

    For brevity I've removed the process launch section (it's the same as the one from above) and the data processing (in the example I just Write-Host it).