5

I find myself chaining these a lot, eg:

do-cmd-one
if($?)
{
do-cmd-two
}
...

then at the end:

if(!$?)
{
   exit 1
}

I assume there's a pattern for this in powershell but I don't know it.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Carbon
  • 3,828
  • 3
  • 24
  • 51

2 Answers2

16

PowerShell (Core) 7.0 introduced Bash-like && and || operators called pipeline-chain operators.

They will not be back-ported to Windows PowerShell, however, as the latter will generally see no new features.

In short, instead of:

do-cmd-one; if ($?) { do-cmd-two }

Note: Up to PowerShell 7.1, the more robust formulation is actually
do-cmd-one; if ($LASTEXITCODE -eq 0) { do-cmd-two }, for the reasons explained in this answer.

you can now write:

do-cmd-one && do-cmd-two

&& (AND) and || (OR) implicitly operate on each command's implied success status, as reflected in automatic Boolean variable $?.

This will likely be more useful with external programs, whose exit codes unambiguously imply whether $? is $true (exit code 0) or $false (any nonzero exit code).

By contrast, for PowerShell commands (cmdlets) $? just reflects whether the command failed as a whole (a statement-terminating error occurred) or whether at least one non-terminating error was reported; the latter doesn't necessarily indicate overall failure.
However, there are plans to allow PowerShell commands to set $? directly, as a deliberate overall-success indicator.

Also note that the following do not work with && and ||:

  • PowerShell's Test-* cmdlets, because they signal the test result by outputting a Boolean rather than by setting $?; e.g.,
    Test-Path $somePath || Write-Warning "File missing" wouldn't work.

  • Boolean expressions, for the same reason; e.g.,
    $files.Count -gt 0 || write-warning 'No files found' wouldn't work.

See this answer for background information, and the discussion in GitHub issue #10917.


There's a syntax caveat: As of this writing, the following will not work:

do-cmd-one || exit 1 # !! Currently does NOT work

Instead, you're forced to wrap exit / return / throw statements in $(...), the so-called subexpression operator:

do-cmd-one || $(exit 1) # Note the need for $(...)

GitHub issue #10967 discusses the reasons for this awkward requirement, which are rooted in the fundamentals of PowerShell's grammar.

mklement0
  • 382,024
  • 64
  • 607
  • 775
-1

Not sure if you like this any better.

if($(do-cmd-one; $?))
{
  do-cmd-two
}
else
{
   exit 1
}

Some other ideas. If actually doesn't check the exit code of a command. But if a command has no output when failing, and has output when successful, it can work. In this case, I'm hiding the error messages. If will always hide a command's regular output.

if(test-connection -Count 1 microsoft.com 2>$null) { 'yes' } # never responds
if(test-connection -Count 1 yahoo.com 2>$null) { 'yes' }
yes

Test-connection happens to have a quiet option that returns a boolean anyway:

if(test-connection -count 1 yahoo.com -quiet) { 'yes' }
yes
if(test-connection -count 1 microsoft.com -quiet) { 'yes' }
js2010
  • 23,033
  • 6
  • 64
  • 66
  • 1
    Yeah, that's still pretty awkward. – Carbon Nov 25 '19 at 23:09
  • This isn't functionally equivalent, because it effectively discards the command's stdout / success-stream output. It would also yield false positives if `do-cmd-one` produced such output while reporting `$false` via `$?`. – mklement0 Nov 25 '19 at 23:24
  • 1
    @mklement0 The question doesn't give an example of using the command output. But it is possible to get a false positive, if the command both gave an output, and also had an exception, like `dir foo,foo2` where foo existed but foo2 didn't. – js2010 Nov 26 '19 at 04:53
  • The code in the question calls the commands without redirecting or capturing the commands' output. Your answer discards the first command's output without even mentioning that fact. The false positives could be avoided with `$null = do-cmd-one`, but the differing output behavior would remain. – mklement0 Nov 26 '19 at 06:39