Exit code from a batch file is not propagated to the parent powershell script

Note: This answer was substantially rewritten after new information came to light.

To complement jazzdelightsme’s effective solution with some general guidance and background information:

  • When calling a batch file from outside cmd.exe, such as from PowerShell, invoke it as cmd /c 'file.cmd ... & exit' or – if string interpolation is needed – as
    cmd /c "file.cmd ... & exit", which then requires escaping embedded " as `"[1]. This ensures that it behaves as it would when called from inside cmd.exe with respect to its exit code (error level), i.e. ensures that the batch file’s exit code reliably becomes cmd.exe‘s process exit code.

    • This post explains the problem and this – obscure – workaround in detail.
    • Note: The simpler workaround based on callcmd /c call file.cmd ... – works in principle, but has an unwanted side effect when (of necessity double-quoted[2]) arguments with ^ characters are passed: Due to use of call, ^ characters (“carets”, strictly speaking circumflex accents, U+005E) are invariably doubled, so that say, argument "a ^ 2", is seen by the batch file as "a ^^ 2"; it is unclear what the purpose of this behavior is, but it has seemingly always existed – see this answer for details on cmd.exe‘s parser.
  • Without the & exit workaround, you’ll only get the desired behavior if you ensure that all code paths exit in one of the following ways – and missing a code path is an easy mistake to make:

    • exit with no arguments, which correctly relays the most recently executed command’s exit code.

      • Caveat: exit without /b instantly exits the cmd.exe instance as a whole, so it isn’t suitable for use inside batch files that may be run interactively or must be callable from other batch files and return control to those batch files.
    • exit /b <code> or exit <code>, where <code> represents the desired exit code, i.e. specifying an explicit exit code, as in jazzdelightsme’s solution.

      • Caveat: Exiting a batch file with exit /b without an explicit <code> argument does not pass the most recently executed command’s exit code through without the cmd /c <batch-file> ... `& exit workaround – see this answer.

Additional background information, in the context of your code:

Bizarrely, with an outside invocation of a batch file without & exit (or call), statements such as if, goto, echo, and endlocal, and even REM (but, curiously, not ::) reset the exit code that cmd.exe later reports to 0 – even though inside that cmd.exe session %ERRORLEVEL% is set as it usually is, meaning that such statements have no impact on the current %ERRORLEVEL% value.

Therefore:

  • When your batch file is run from inside cmd.exe, the specific statements that follow the command that sets %ERRORLEVEL% to 1 (cmd /c dir aaa), i.e. the if, goto, echo and endlocal statements, preserve this value, and %ERRORLEVEL% ends up with value 1 after exiting the batch file.

    • Bizarrely, the error level is set only after the statement involving the batch file call, so that something like file.cmd || exit does not work, because the || operator doesn’t recognize the file.cmd call as having failed. This is the reason that the workaround uses & rather than ||.
  • When your batch file is run from outside cmd.exe, and isn’t invoked with cmd /c "<batch-file> ... & exit" (or cmd /c call <batch-file> ...), the same statements that follow the %ERRORLEVEL%-setting command suddenly reset the exit code that cmd.exe itself later reports to 0, instead of preserving the post-batch-file-execution %ERRORLEVEL% value.


[1] Situationally, you can get away with cmd /c <batch-file> ... `& exit, i.e. without enclosing the arguments to /c in a single, overall string, but that breaks if the batch-file path needs double-quoting and at least one double-quoted pass-through argument is also present.

[2] ^ chars. in unquoted arguments, as usual, are interpreted as cmd.exe‘s escape character and therefore removed.

Leave a Comment