Unfortunately asynchronous reading is not that easy if you want to do it properly. If you call WaitForExit() without timeout you could use something like this function I wrote (based on C# code):
function Invoke-Executable {
# Runs the specified executable and captures its exit code, stdout
# and stderr.
# Returns: custom object.
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]$sExeFile,
[Parameter(Mandatory=$false)]
[String[]]$cArgs,
[Parameter(Mandatory=$false)]
[String]$sVerb
)
# Setting process invocation parameters.
$oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$oPsi.CreateNoWindow = $true
$oPsi.UseShellExecute = $false
$oPsi.RedirectStandardOutput = $true
$oPsi.RedirectStandardError = $true
$oPsi.FileName = $sExeFile
if (! [String]::IsNullOrEmpty($cArgs)) {
$oPsi.Arguments = $cArgs
}
if (! [String]::IsNullOrEmpty($sVerb)) {
$oPsi.Verb = $sVerb
}
# Creating process object.
$oProcess = New-Object -TypeName System.Diagnostics.Process
$oProcess.StartInfo = $oPsi
# Creating string builders to store stdout and stderr.
$oStdOutBuilder = New-Object -TypeName System.Text.StringBuilder
$oStdErrBuilder = New-Object -TypeName System.Text.StringBuilder
# Adding event handers for stdout and stderr.
$sScripBlock = {
if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
$Event.MessageData.AppendLine($EventArgs.Data)
}
}
$oStdOutEvent = Register-ObjectEvent -InputObject $oProcess `
-Action $sScripBlock -EventName 'OutputDataReceived' `
-MessageData $oStdOutBuilder
$oStdErrEvent = Register-ObjectEvent -InputObject $oProcess `
-Action $sScripBlock -EventName 'ErrorDataReceived' `
-MessageData $oStdErrBuilder
# Starting process.
[Void]$oProcess.Start()
$oProcess.BeginOutputReadLine()
$oProcess.BeginErrorReadLine()
[Void]$oProcess.WaitForExit()
# Unregistering events to retrieve process output.
Unregister-Event -SourceIdentifier $oStdOutEvent.Name
Unregister-Event -SourceIdentifier $oStdErrEvent.Name
$oResult = New-Object -TypeName PSObject -Property ([Ordered]@{
"ExeFile" = $sExeFile;
"Args" = $cArgs -join " ";
"ExitCode" = $oProcess.ExitCode;
"StdOut" = $oStdOutBuilder.ToString().Trim();
"StdErr" = $oStdErrBuilder.ToString().Trim()
})
return $oResult
}
It captures stdout, stderr and exit code. Example usage:
$oResult = Invoke-Executable -sExeFile 'ping.exe' -cArgs @('8.8.8.8', '-a')
$oResult | Format-List -Force
For more info and alternative implementations (in C#) read this blog post.