25

Why does a PowerShell script not end when there is a non-zero exit code when using the call operator and $ErrorActionPerference = "Stop"?

Using the following example, I get the result managed to get here with exit code 1:

$ErrorActionPreference = "Stop"

& cmd.exe /c "exit 1"

Write-Host "managed to get here with exit code $LASTEXITCODE"

The Microsoft documentation for the call operator does not discuss what should happen when using call operator, it only states the following:

Runs a command, script, or script block. The call operator, also known as the "invocation operator," lets you run commands that are stored in variables and represented by strings. Because the call operator does not parse the command, it cannot interpret command parameters.


Additionally, if this is expected behaviour, is there any other way to have the call operator cause an error rather than let it continue?

li ki
  • 342
  • 3
  • 11
Panda TG Attwood
  • 1,468
  • 2
  • 16
  • 31
  • `if ($LASTEXITCODE -eq 1) { throw "Exit code is 1" }` ? – Jelphy Oct 31 '17 at 10:56
  • 2
    As anything but 0 is an error code: `if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" }` – henrycarteruk Oct 31 '17 at 10:59
  • 2
    It *must* happen this way because there's no way to pass `-ErrorAction` to an external command, nor is it even standardized what error codes mean. If any non-zero exit code caused a PowerShell error, you'd be obliged to wrap every external command in a `try .. catch` just to get work done. This would be massively annoying. PowerShell is still primarily a shell language, not a general programming language. (`cmd.exe` doesn't care about exit codes either, same story -- you're free to check them yourself, of course.) – Jeroen Mostert Oct 31 '17 at 11:10
  • Setting `$ErrorActionPreference` to `Stop` does not work when a native command (i.e., running an executable) returns an error. You need to check [`$?`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables#section-1) and/or [`$LASTEXITCODE`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables#lastexitcode). See also [windows - How to stop a PowerShell script on the first error?](https://stackoverflow.com/q/9948517). – li ki Oct 01 '21 at 15:04

3 Answers3

25

The return code is not a PowerShell error - it's seen the same way as any other variable.

You need to then act on the variable and throw an error using PowerShell for you script to see it as a terminating error:

$ErrorActionPreference = "Stop"

& cmd.exe /c "exit 1"

if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" }
henrycarteruk
  • 12,708
  • 2
  • 36
  • 40
  • 3
    is there any syntax sugar I can use to make this nice? Something like `& { cmd.exe /C "exit 1" } -FailIfNonZero`? – Groostav Sep 12 '19 at 22:14
  • 1
    is there any powershell switch which is equivalent to `set -e` in bash, i.e. "exit (here: throw) immediately if pipeline (command, list or compound command) returns a non-zero status" ? – rychu Sep 29 '22 at 13:24
19

In almost all my PowerShell scripts, I prefer to "fail fast," so I almost always have a small function that looks something like this:

function Invoke-NativeCommand() {
    # A handy way to run a command, and automatically throw an error if the
    # exit code is non-zero.

    if ($args.Count -eq 0) {
        throw "Must supply some arguments."
    }

    $command = $args[0]
    $commandArgs = @()
    if ($args.Count -gt 1) {
        $commandArgs = $args[1..($args.Count - 1)]
    }

    & $command $commandArgs
    $result = $LASTEXITCODE

    if ($result -ne 0) {
        throw "$command $commandArgs exited with code $result."
    }
}

So for your example I'd do this:

Invoke-NativeCommand cmd.exe /c "exit 1"

... and this would give me a nice PowerShell error that looks like:

cmd /c exit 1 exited with code 1.
At line:16 char:9
+         throw "$command $commandArgs exited with code $result."
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (cmd /c exit 1 exited with code 1.:String) [], RuntimeException
    + FullyQualifiedErrorId : cmd /c exit 1 exited with code 1.
Phil
  • 6,561
  • 4
  • 44
  • 69
0

You can throw an error on the same line of code if the command failed:

& cmd.exe /c "exit 1"; if(!$?) { throw }

Automatic variables: $?

Winand
  • 2,093
  • 3
  • 28
  • 48