0

I am working on a function that will invoke plink.exe to run commands on a remote non-Windows device. (In fact, this will be a more generalized function, but this is the use-case I am currently dealing with). In case the command does not work as expected, I am capturing the output of the EXE as an object that I can manipulate in my function and/or parent script.

The problem I am having is that when I capture the output, it seems to be incomplete.

Somehow I am not getting all the StandardOutput that the process generates. I can be sure of this since I am testing with plink.exe -v which simply outputs the command usage help and is predictable each time.

Here is some sample code, cut down from what I am working on, but which exhibits this behaviour when I run in PowerShell ISE 5.0.

function Set-ProcessStartInfo ($FileName,$Arguments) {
  $ProcessStartInfo = New-Object System.Diagnostics.ProcessStartInfo
  $ProcessStartInfo.FileName        = $FileName
  $ProcessStartInfo.Arguments       = $Arguments
  $ProcessStartInfo.UseShellExecute = $false
  $ProcessStartInfo.WindowStyle     = "Hidden"
  $ProcessStartInfo.RedirectStandardError  = $true
  $ProcessStartInfo.RedirectStandardOutput = $true

  return $ProcessStartInfo
}

function Write-StdOut ($LastEvent = 0){
  foreach ($EventId in ($StdOut.Keys | Sort | ?{$_ -gt $LastEvent})) {
    Write-Host "$EventId`t$($StdOut.$EventId)"
    $LastEvent = $EventId
  }
  return $LastEvent
}

$Process = New-Object System.Diagnostics.Process
$Process.StartInfo = (Set-ProcessStartInfo "plink.exe" "-v")

$Global:StdOut = @{}
$StdOutLastEvent = 0

$StdOutAction = {
  if(-not $StdOut.ContainsKey($Event.EventIdentifier)) {
    $StdOut.Add([System.Int64]$Event.EventIdentifier, [System.String]$EventArgs.Data)
  }
}

$StdOutJob = Register-ObjectEvent -InputObject $Process -EventName OutputDataReceived -Action $StdOutAction

$Process.Start() | Out-Null
$Process.BeginOutputReadLine() | Out-Null

Clear-Host;Write-host;Write-Host

$Count = 0

do{
  $Count++

  Write-Verbose "Loop $Count starting"

  Sleep -Milliseconds 250

  $HasExisted = $Process.HasExited

  Receive-Job $StdOutJob.Id

  $StdOutLastEvent = Write-StdOut -LastEvent $StdOutLastEvent

  Write-Verbose "Loop $Count complete"

} while (-not $HasExisted)

# $StdOutLastEvent = Write-StdOut -LastEvent $StdOutLastEvent

Stop-Job $StdOutJob.Id
Remove-Job $StdOutJob.Id

This gives the following:

VERBOSE: Loop 1 starting
1370    PuTTY Link: command-line connection utility
1371    Development snapshot 2008-12-04:8371
1372    Usage: plink [options] [user@]host [command]
1373           ("host" can also be a PuTTY saved session name)
1374    Options:
1375      -V        print version information and exit
1376      -pgpfp    print PGP key fingerprints and exit
VERBOSE: Loop 1 complete

There is a load of output missing (approx. 30 lines of text)

Workaround

If I uncomment the # $StdOutLastEvent = Write-StdOut -LastEvent $StdOutLastEvent line at the end, I do get the remaining ~30 lines of text. I assume this has something to do with the asynchronous nature of BeginOutputReadLine(), and the time it takes for the Job created by Register-ObjectEvent to complete.

At the moment I am treating this as a workaround, since I have no way of knowing that I have collected everything.

Is there a way that I can be sure that I have received everything?

Charlie Joynt
  • 4,411
  • 1
  • 24
  • 46
  • PS. Note that I do want to keep reading this output *while the process is running* since in many cases I will be trying to trace the output of long-running processes. In other words, I can't use solutions that involve waiting for a process to complete and then taking all output. – Charlie Joynt May 12 '16 at 13:29
  • 1
    Add output printing after `Stop-Job`. – user4003407 May 12 '16 at 14:55
  • It seems that the Job remains in a running state; I suppose my question could equally be: "When should I stop the job?" or "Will stopping the job (immediately after the process has exited) prevent StandardOutput being handled by the StdOutAction block?" – Charlie Joynt May 12 '16 at 15:26
  • 1
    `Stop-Job` for event job ensures that all already raised events are processed (I verify that by decompiling). If you want to ensure that all `OutputDataReceived` are raised before process exit, than you probably should use `WaitForExit` method (the one with infinite timeout). That method wait for EOF for async streams, so it also wait for any child process (which inherit parent output handle) to exit or close their output. – user4003407 May 12 '16 at 21:07
  • Many thanks, both for your explanation and for the advice. :-) – Charlie Joynt May 12 '16 at 21:20

0 Answers0