0

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.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Ray Depew
  • 573
  • 1
  • 9
  • 22
  • 1
    `$?` tells you if the last command completed successfully or not. `if (jfrog config use Dummy)` will be true only if `jfrog config use Dummy` returns any objects/strings to the success stream – Daniel Sep 13 '21 at 18:42
  • 1
    @Daniel That's actually not true. [It looks like at least as of PS 5.1](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-5.1#section-1) `$?` should return `$true` if the last command was an external command and the value of `$LASTEXITCODE` is `0`, and `$False` for any other value. – codewario Sep 13 '21 at 18:45
  • 2
    @BendertheGreatest, I see. Isn't that basically the same thing for the most part? $LASTEXITCODE of 0 is usually used by native applications to indicate success while any other code indicates failure. I know not all applications conform strictly to this, but for the most part this is expected. Anyways, my point is `$?` and `if (jfrog config use Dummy)` are not the same thing. The latter will return true only if objects are received on the success steam and has nothing to do with whether the command succeeded or not. – Daniel Sep 13 '21 at 18:50
  • 1
    I totally misread your first comment, my bad. I thought you said cmdlet not command – codewario Sep 13 '21 at 18:54
  • (Quickly googling for "success stream") Ohhhhh! So if jfrog outputs its Error message on the Error stream, everything acts as expected. And if jfrog outputs its Info message on the Success stream, everything *would* act as expected. But if jfrog outputs its Info message on, say, the Information stream, (hence, `Info`) instead of the Success stream, that could cause the behavior I'm seeing. Is that right? – Ray Depew Sep 13 '21 at 18:58
  • (Link to the official docs: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_output_streams?view=powershell-7.1) – Ray Depew Sep 13 '21 at 18:59
  • This issue https://stackoverflow.com/questions/51609888/robocopy-lastexitcode-inconsistent-behaviors-depending-if-the-destination-pa?rq=1 repeats the caveat about external commands vs. cmdlets. Similar issue? – Ray Depew Sep 13 '21 at 19:06

1 Answers1

2

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.

codewario
  • 19,553
  • 20
  • 90
  • 159
  • Could be a bug on JFrog's part. I'll log an issue with them, and then rewrite my code to use $LASTEXITCODE. – Ray Depew Sep 13 '21 at 19:02
  • 1
    I stand corrected, STDERR *does* go to the Error Stream. I also just did a quick test with `ping` and I get true both times. So what I'm thinking here is that jfrog is outputting everything to STDERR. I'm actually vaguely recalling something about this from a few years back, but my team doesn't manage Artifactory so I don't recall all of the details. – codewario Sep 13 '21 at 19:14
  • In local invocations in a console / terminal, stderr lines are sensibly passed straight through to the display (but not in other PowerShell hosts, notably not in remoting and background jobs and the SDK, where they indeed go through PowerShell's error stream), given that they can't be assumed to be _errors_ . With a `2>` redirection you can selective redirect stderr output. Before PowerShell 7.2, this - unfortunately - entailed routing stderr output via PowerShell's error stream, which had unexpected side effects with `$ErrorActionPreference = 'Stop'` in effect. – mklement0 Sep 13 '21 at 22:57
  • 1
    Quibble: "Any output evaluates to $true" obviously doesn't apply to outputs such as `0` and `$false`, and even `, 0` or `, $false` (single-element collections are unwrapped). – mklement0 Sep 13 '21 at 22:58