'I've built a PowerShell-Script which can handle output of MS Windows executable assemblies (*.exe) in real-time.
Methods like "WaitForExit()" were not in scope, because such methods will block the event/output! Using such methods will only show the result after exit. (can be tested easily by yourself with e.g. "ping.exe")
Maybe somebody can bring in some additional improvements to avoid unexpected behaviors for my generic script. Especially for particular executables.
Especially (and that's my main question):
How can I fetch (by events) host updates when using 7z.exe to check the integrity of an archive:
e.g.
7z t "D:\<someBigArchive.7z"
-> In CMD i get the percentage of the running process, but not with that script.
All other real-time output is working when the application is writing new lines to console.
That's my current PS script:
param(
[string] $appFilePath = 'ping.exe',
[string] $appArguments = 'google.com',
[string] $appWorkingDirPath = '',
[int] $consoleOutputEncoding = 850 # 850 = default windows console output encoding (useful for e.g 7z.exe). use "<=0" for host's default encooding.
)
if (!$consoleOutputEncoding -le 0) {
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding($consoleOutputEncoding)
}
$eventScriptBlock = {
# received app output
$receivedAppData = $Event.SourceEventArgs.Data
# Write output as stream to console in real-time (without -stream parameter output will produce blank lines!)
# (without "Out-String" output with multiple lines at once would be displayed as tab delimited line!)
Write-Host ($receivedAppData | Out-String -Stream)
<#
< Insert additional real-time processing steps here.
< Since it''s in an entirely different scope (not child), variables of parent scope won't be populated to that child scope and scope "$script:" won't work as well. (scope "$global:" would work but should be avoided!)
< Modify/Enhance variables "*MessageData" (see below) before registering the event to access such variables.
#>
# add received data to stringbuilder definded in $stdOutEventMessageData and $stdErrEventMessageData
$Event.MessageData.outStringBuilder.AppendLine($receivedAppData)
}
# MessageData parameters for default events (used for event input and output)
$stdOutEventMessageData = @{
# used for adding output within events to stringbuilder (OUT) for further usage
outStringBuilder = [System.Text.StringBuilder]::new()
#< add additional properties if necessary. Can be used as input and output in $eventScriptBlock ($Event.MessageData.*)
#< ....
}
# MessageData parameters for error events (used for event input and output)
$stdErrEventMessageData = @{
# used for adding output within events to stringbuilder (OUT) for further usage
outStringBuilder = [System.Text.StringBuilder]::new()
#< add additional properties if necessary. Can be used as input and output in $eventScriptBlock ($Event.MessageData.*)
#< ....
}
#######################################################
#region Process-Definition, -Start and Event-Subscriptions (Adaptions in that region should be avoided!)
#------------------------------------------------------
try {
$appProcess = [System.Diagnostics.Process]::new()
$appProcess.StartInfo = @{
Arguments = $appArguments
WorkingDirectory = $appWorkingDirPath
FileName = $appFilePath # mandatory
RedirectStandardOutput = $true # mandatory = $true
RedirectStandardError = $true # mandatory = $true
#< RedirectStandardInput = $true # leave commented; only useful in some circumstances. Didn''t find any use, but mentioned in: https://stackoverflow.com/questions/8808663/get-live-output-from-process
UseShellExecute = $false # mandatory = $false
CreateNoWindow = $true # mandatory = $true
}
$stdOutEvent = Register-ObjectEvent -InputObject $appProcess -Action $eventScriptBlock -EventName 'OutputDataReceived' -MessageData $stdOutEventMessageData
$stdErrEvent = Register-ObjectEvent -InputObject $appProcess -Action $eventScriptBlock -EventName 'ErrorDataReceived' -MessageData $stdErrEventMessageData
[void]$appProcess.Start()
$appProcess.BeginOutputReadLine()
$appProcess.BeginErrorReadLine()
while (!$appProcess.HasExited) {
# Don't use method "WaitForExit()"! This will not show the output in real-time as it blocks the output stream!
# using "Sleep" from System.Threading.Thread class for short sleep times below 1/1.5 seconds is better than "Start-Sleep" in terms of PS overhead/performance on init (Test it yourself)
# (sleep will block console output --> don't set too high; but also not too low for performance reasons in long running actions)
[System.Threading.Thread]::Sleep(250)
#< maybe timeout ...
}
} finally {
if (!$appProcess.HasExited) {
$appProcess.Kill() # WARNING: Entire process gets killed! Review and adapt!
}
if ($stdOutEvent -is [System.Management.Automation.PSEventJob]) {
Unregister-Event -SourceIdentifier $stdOutEvent.Name
}
if ($stdErrEvent -is [System.Management.Automation.PSEventJob]) {
Unregister-Event -SourceIdentifier $stdErrEvent.Name
}
}
#------------------------------------------------------
#endregion
#######################################################
$stdOutText = $stdOutEventMessageData.outStringBuilder.ToString() # final output for further usage
$stErrText = $stdErrEventMessageData.outStringBuilder.ToString() # final errors for further usage