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 ascmd /c 'file.cmd ... & exit'
or – if string interpolation is needed – ascmd /c "file.cmd ... & exit"
, which then requires escaping embedded"
as`"
[1]. This ensures that it behaves as it would when called from insidecmd.exe
with respect to its exit code (error level), i.e. ensures that the batch file’s exit code reliably becomescmd.exe
‘s process exit code.- This post explains the problem and this – obscure – workaround in detail.
- Note: The simpler workaround based on
call
–cmd /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 ofcall
,^
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 oncmd.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 thecmd.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.
- Caveat:
-
exit /b <code>
orexit <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 thecmd /c <batch-file> ... `& exit
workaround – see this answer.
- Caveat: Exiting a batch file with
-
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%
to1
(cmd /c dir aaa
), i.e. theif
,goto
,echo
andendlocal
statements, preserve this value, and%ERRORLEVEL%
ends up with value1
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 thefile.cmd
call as having failed. This is the reason that the workaround uses&
rather than||
.
- Bizarrely, the error level is set only after the statement involving the batch file call, so that something like
-
When your batch file is run from outside
cmd.exe
, and isn’t invoked withcmd /c "<batch-file> ... & exit"
(orcmd /c call <batch-file> ...
), the same statements that follow the%ERRORLEVEL%
-setting command suddenly reset the exit code thatcmd.exe
itself later reports to0
, 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.