Home > Enterprise >  Use PowerShell to handle errors generated by a Java CLI application
Use PowerShell to handle errors generated by a Java CLI application

Time:09-27

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.
  • 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 is 0 (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 the java 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 the System.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 to Start-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
}
  • Related