You can call and catch errors of native applications in many ways.
Some examples:
1. Most easy
with no process handling, no distinguishing between success and error.
$nativeAppFilePath = 'ping.exe'
# parameters as collection. Parameter-Value pairs with a space in between must be splitted into two.
$nativeAppParam= @(
'google.com'
'-n'
'5'
)
# using powershell's call operator '&'
$response = & $nativeAppFilePath $nativeAppParam
$response
2. Easy
, same as 1., but distinguishing between success and error possible.
$nativeAppFilePath = 'ping.exe'
# parameters as collection. Parameter-Value pairs with a space in between must be splitted into two.
$nativeAppParam= @(
'google2.com'
'-n'
'5'
)
# using powershell's call operator '&' and redirect the error stream to success stream
$nativeCmdResult = & $nativeAppFilePath $nativeAppParam 2>&1
if ($LASTEXITCODE -eq 0) {
# success handling
$nativeCmdResult
} else {
# error handling
# even with redirecting the error stream to the success stream (above)..
# $LASTEXITCODE determines what happend if returned as not "0" (depends on application)
Write-Error -Message "$LASTEXITCODE - $nativeCmdResult"
}
! Now two more complex snippets, which doesn't work with "ping.exe" (but most other applications), because "ping" doesn't raise error events.
3. More complex
with process handling, but still process blocking until the application has been finished.
$nativeAppProcessStartInfo = @{
FileName = 'ping.exe' # either OS well-known as short name or full path
Arguments = @(
'google.com'
'-n 5'
)
RedirectStandardOutput = $true # required to catch stdOut stream
RedirectStandardError = $true # required to catch stdErr stream
UseShellExecute = $false # required to redirect streams
CreateNoWindow = $true # does what is says (background work only)
}
try {
$nativeApp= [System.Diagnostics.Process]@{
EnableRaisingEvents = $true
StartInfo = $nativeAppProcessStartInfo
}
[void]$nativeApp.Start()
# Warning: As soon as the buffer gets full, the application could stuck in a deadlock. Then you require async reading
# see: https://stackoverflow.com/a/7608823/13699968
$stdOut = $nativeApp.StandardOutput.ReadToEnd()
$stdErr = $nativeApp.StandardError.ReadToEnd()
# To avoid deadlocks with synchronous read, always read the output stream first and then wait.
# see: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardoutput?redirectedfrom=MSDN&view=net-5.0#remarks
$nativeApp.WaitForExit()
if ($stdOut.Result) {
# success handling
$stdOut.Result
}
if ($stdErr.Result) {
# error handling
$stdErr.Result
}
} finally {
$nativeApp.Dispose()
}
4. The most complex
with realtime output & reaction, capturing, and so on...
This time with wmic.exe and a nonsense parameter as example.
$appFilePath = 'wmic.exe'
$appArguments = @(
'someNonExistentArgument'
)
$appWorkingDirPath = ''
# handler for events of process
$eventScriptBlock = {
# $Event is an automatic variable. Only existent in scriptblocks used with Register-ObjectEvent
# 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 is in a different scope, variables changed in this scope will not get changed in parent scope
and scope "$script:" will not work as well. (scope "$global:" would work but should be avoided!)
Modify/Enhance variables "*MessageData" (see below) before registering the event to modify such variables.
#>
# add received data to stringbuilder definded in $stdOutEventMessageData and $stdErrEventMessageData
$Event.MessageData.Data.AppendLine($receivedAppData)
}
# MessageData parameters for events of success stream
$stdOutEventMessageData = @{
# useful for further usage after application has been exited
Data = [System.Text.StringBuilder]::new()
# add additional properties necessary in event handler scriptblock above
}
# MessageData parameters for events of error stream
$stdErrEventMessageData = @{
# useful for further usage after application has been exited
Data = [System.Text.StringBuilder]::new()
# add additional properties necessary in event handler scriptblock above
}
#######################################################
#region Process-Definition, -Start and Event-Subscriptions
#------------------------------------------------------
try {
$appProcessStartInfo = @{
FileName = $appFilePath
Arguments = $appArguments
WorkingDirectory = $appWorkingDirPath
RedirectStandardOutput = $true # required to catch stdOut stream
RedirectStandardError = $true # required to catch stdErr stream
# RedirectStandardInput = $true # only useful in some circumstances. Didn't find any use yet, but mentioned in: https://stackoverflow.com/questions/8808663/get-live-output-from-process
UseShellExecute = $false # required to redirect streams
CreateNoWindow = $true # does what is says (background work only)
}
$appProcess = [System.Diagnostics.Process]@{
EnableRaisingEvents = $true
StartInfo = $appProcessStartInfo
}
# to obtain available events of an object / type, read the event members of it: "Get-Member -InputObject $appProcess -MemberType Event"
$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()
# async reading
$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 for short sleep times below 1/1.5 seconds is better than
# "Start-Sleep" in terms of PS overhead/performance (Test it yourself)
[System.Threading.Thread]::Sleep(250)
# maybe timeout ...
}
} finally {
if (!$appProcess.HasExited) {
$appProcess.Kill() # WARNING: Entire process gets killed!
}
$appProcess.Dispose()
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.Data.ToString() # final output for further usage
$stdErrText = $stdErrEventMessageData.Data.ToString() # final errors for further usage