31

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?

4
  • if ($LASTEXITCODE -eq 1) { throw "Exit code is 1" } ? Commented Oct 31, 2017 at 10:56
  • 2
    As anything but 0 is an error code: if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" } Commented Oct 31, 2017 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.) Commented Oct 31, 2017 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 $? and/or $LASTEXITCODE. See also windows - How to stop a PowerShell script on the first error?. Commented Oct 1, 2021 at 15:04

4 Answers 4

27

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" }
Sign up to request clarification or add additional context in comments.

2 Comments

is there any syntax sugar I can use to make this nice? Something like & { cmd.exe /C "exit 1" } -FailIfNonZero?
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" ?
24

EDIT (years later): It looks like Microsoft has added the ability to fail on non-zero exit codes: https://stackoverflow.com/a/9949105/138757

Original answer:


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.

7 Comments

I cannot believe that this is not the default behavior. PowerShell is the YOLO of shell scripting. What a mess.
To be fair, Windows isn't held together with boatloads of scripts like other Unix-like OSes. Many developers of CLI tools don't even think about exit codes, because they tend to build more monolithic programs with less scripting glue. So in a way, moving away from "native" commands altogether and encouraging people to just use PowerShell cmdlets is probably one of the smartest decisions Microsoft made when designing PowerShell.
No. It's a complete disregard of what people have done since the 70s and a failure to realize it still needs to exist in a world where binaries exist. Like, the most normal thing to do in a shell is to launch programs. How is that not a first class concept in PowerShell!? We're talking exit codes for God's sake! Also, in what universe is the default in the event of failure to just proceed as if nothing happened? That's what PowerShell does. I'm utterly perplexed by these design decisions.
Again, to be fair, the default to proceed in the event of a failure is entirely POSIX. Both borne and bash shells do that, unless you do set -e at the beginning of a script. In any case, I don't mind if you disagree. I would just caution you not to fall into the trap of Chesterton's Fence -- don't criticize a design decision without first talking to the people who made the decision.
Funny you would bring up Chesteron's Frence, big fan but in the case of bash you at least have that option. PowerShell was originally designed by Jeffrey Snover and I actually talked to him in 2003 and voiced my concerns. They added back && in PowerShell 6-7 (some 20 years later) because you know, Chesteron's Frence.
|
3

As of PowerShell 7.4 there's a $PSNativeCommandUseErrorActionPreference variable that can be changed to $true to make non-zero exit codes behave with respect to $ErrorActionPreference.

See about_Preference_Variables

Comments

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: $?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.