Ray Depew
Ray Depew

Reputation: 647

Why is PowerShell not treating this $? exit code correctly?

I want to write a PowerShell script to select a JFrog service connection, but the script is not behaving the way I expect it to behave.

When I run the jfrog config use command manually, it works as expected:

PS C:\Users\rdepew> jfrog config use Dummy
[Info] Using server ID 'Dummy' (https://foo.jfrog.io/).
PS C:\Users\rdepew> $?
True

I expect to be able to use the $?, which always returns true or false, to detect whether or not jfrog config use has executed successfully. This snippet illustrates the problem I've got:

PS C:\Users\rdepew> if (jfrog config use Dummy) {
>>   Write-Host Dummy exists
>> } else {
>>   Write-Host Dummy doesnt exist but I dont believe it
>> }
[Info] Using server ID 'Dummy' (https://foo.jfrog.io/).
Dummy doesnt exist but I dont believe it

The [Info] line indicates that JFrog is in fact using server connection 'Dummy'. I assume that the $? exit code evaluates as true, just like it did in my manual execution. But my if/else test takes the "if false" branch. Why?

MORE INFO: If I use the name of a nonexistent service connection, I get the expected behavior:

PS C:\Users\rdepew> if (jfrog config use Bogus) {
>>   Write-Host Bogus exists
>> } else {
>>   Write-Host Bogus doesnt exist
>> }
[Error] Could not find a server with ID 'Bogus'.
Bogus doesnt exist

I don't understand why PS jumps to the else clause for both True and False cases.

STILL MORE INFO

Thanks to the first answers and comments, I understand a little more. I can rewrite my snippet like this:

jfrog config add Dummy
if ($LASTEXITCODE=0) {
  etc.

But I'm still confused about $? behavior. From the command line, $? behaves like this:

PS C:\Users\rdepew> jfrog config use Dummy
[Info] Using server ID 'Dummy' (https://foo.jfrog.io/).
PS C:\Users\rdepew> $?
True
PS C:\Users\rdepew> jfrog config use Bogus
[Error] Could not find a server with ID 'Bogus'.
PS C:\Users\rdepew> $?
False

... which is what one would expect.

Upvotes: 0

Views: 1030

Answers (1)

codewario
codewario

Reputation: 21418

In PowerShell, $? returns $true if the prior cmdlet completed without error or $false if it presented any errors. For external commands, it will return the result of $LASTEXITCODE -eq 0.

However, when you put a command (external or otherwise) as the condition in an if statement it doesn't check whether the command succeeded but rather the truthiness of the result. If a command returns a proper $true or $false boolean this is straightforward, otherwise, it will convert the output on the success stream to a boolean value. Any excepting the following output evaluates to $true and, $null, 0, empty strings, and empty arrays evaluate to $false.

What I'm not sure of though is why both commands aren't producing a $true output at least when run as the if condition. Another way to write your code would be to run jfrog outside of the if and just check the result of $LASTEXITCODE:

$output = jfrog config use Dummy

if ( $LASTEXITCODE -eq 0 ) {
  Write-Host "Dummy exists"
} else {
  Write-Host "Dummy doesnt exist but I dont believe it"
}

If other exit codes are acceptable, use this one weird trick to check multiple values without having to chain multiple evals of $LASTEXITCODE together:

# I use this often when checking the result of MSIEXEC
if ( $LASTEXITCODE -in @(0, 3010) ) {
....
}

Basically, this just says to check that $LASTEXITCODE exists in the array on the right hand side of -in. You can use -notin to negate the evaluation. The -contains/-notcontains operators are also an option, you just have to reverse the operands from -in/-notin.


After some additional testing, and vaguely recalling this issue from a few years back at work, I believe jfrog is writing everything to STDERR which would explain your behavior (you can test with $var = jfrog blah blah blah and see if $var contains the output or if it still gets written to console).

If you want to alleviate this, say, in the event you want to capture the output, redirect the Error Stream (basically STDERR) to the Success Stream (basically STDOUT):

$output = jfrog blah blah blah 2>&1

However, I would still rely on checking $LASTEXITCODE to check for failure rather than the output or $?. Checking for output results in similar issues presented in this question, and checking $? requires you to be mindful of updates to your code in the future, since you could very easily slip another command between the execution and result evaluation.

I have a comprehensive answer here on the different output streams and redirection in general which you might find useful.

Upvotes: 2

Related Questions