I using PowerShell to call a Java CLI application.
I'd like to be able to capture Java errors and handle them in PowerShell. I'm simulating this by trying to get the version of Java.
If I use a valid argument (--version
)
# --version - print product version to the output stream and exit
Start-Process -FilePath java -NoNewWindow -ArgumentList '--version' -RedirectStandardOutput ~\Desktop\stdout.txt
stdout.txt
contains the expected text:
Get-Content ~\Desktop\stdout.txt
openjdk 11.0.3 2019-04-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.3 7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.3 7, mixed mode)
If I use an invalid argument (-v
)
# -v; invalid argument
Start-Process -FilePath java -NoNewWindow -ArgumentList '-v' -RedirectStandardError ~\Desktop\stderr.txt
stderr.txt
contains the expected text:
Get-Content ~\Desktop\stderr.txt
Unrecognized option: -v
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Unfortunately, I can't seem to get this to work in a try/catch block:
try {
# exception isn't raised
Start-Process -FilePath java -NoNewWindow -ArgumentList '-v' -ErrorAction Stop
}
catch {
Write-Host "ERROR: $( $_.Exception.Message )" -ForegroundColor Red
}
Is there a way to get this to work as I would like?
CodePudding user response:
In order to synchronously execute console applications (such as java
), in the current console window, including the ability to directly capture their output, call them directly, do not use Start-Process
(or the System.Diagnostics.Process
API it is based on) - see this answer. GitHub docs issue #6239 provides guidance on when use of Start-Process
is and isn't appropriate.
Thus, for synchronous execution use the following, which:
directly prints stderr output (as well as stdout output), while also allowing it to be suppressed (
2>$null
) or redirected to a file (2>stderr.txt
), or mixed into the stdout / success output stream (2>&1
).- Note: It would be helpful if stderr output could be captured in a variable, with syntax such as
2>variable:stderr
, which isn't supported as of PowerShell 7.2.x - see GitHub issue #4332.
- Note: It would be helpful if stderr output could be captured in a variable, with syntax such as
reports the console application process' exit code in the automatic
$LASTEXITCODE
variable; given that stderr output isn't only used for actual errors, but also for other non-data purposes, such as status information, success vs. failure of a call to an external (console) application should only ever be inferred from whether$LASTEXITCODE
is0
(indicates success) or is nonzero (indicates failure, by convention).
# Invokes java synchronously with arguments, in the current console window.
# Prints both stdout and stderr to the console, unless you capture or redirect it.
java -v
# $LASTEXITCODE now contains the process exit code.
# $LASTEXITCODE -eq 0 tells you if the call succeeded.
As for what you tried:
Since you're using
Start-Process
without-Wait
, the execution is asynchronous, so you cannot know the full content of the stdout and stderr streams until after thejava
process has exited.As stated, only the process exit code should be used to determine success vs. failure, which would require you to add
-PassThru
in order to obtain that exit code from the.ExitCode
property of theSystem.Diagnostics.Process
instance that is then returned - assuming you either used-Wait
too or have first ensured that the.HasExited
property returns$true
.Applying
-ErrorAction Stop
toStart-Process
is unrelated to whether the application invoked reports an error.In fact, even if
Start-Process
fails to even find the specified executable and therefore fundamentally cannot even invoke it, it will report a statement-terminating error, to which the common-ErrorAction
parameter does not apply - see this answer for details.It is only if invocation itself fails that a
try { ... } catch { ... }
would take effect (independently of the-ErrorAction
argument).
As for what you're probably looking for:
- Using a file to capture stderr output.
java -v 2>~\Desktop\stderr.txt
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: $(Get-Content -Raw ~\Desktop\stderr.txt)" -ForegroundColor Red
}
- If you want to avoid use of a file:
$stdout, $stderr = (java -v 2>&1).Where({ $_ -is [string] }, 'Split' })
$stdout # Output collected stdout lines.
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: $stderr" -ForegroundColor Red
}
- If you want to avoid use of a file and stream stdout output (rather than collect it in full first and then output):
$stderr = [Collections.Generic.List[string]]::new()
java -v 2>&1 |
ForEach-Object { if ($_ -is [string]) { $_ } else { $stderr.Add($_) } }
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: $stderr" -ForegroundColor Red
}