5

Consider a command script named t.cmd that consists solely of these 2 lines:

@exit /b 123
@echo If you see this, THEN EXIT FAILED..

So, the script merely sets the exit code of the script's execution process to 123, but does not kill cmd.exe. The final echo confirms that exit actually causes an immediate return (its output should not appear).

Now execute this script, and then print out the %errorlevel%:

>t.cmd

>echo %errorlevel%
123

So far so good: everything behaves exactly as expected.

But now execute the above all on one line, using && for conditional execution:

>t.cmd && echo %errorlevel%
123

I do NOT expect this: if t.cmd really returns with a non-0 exit code, then it should stop everything after that && (i.e. the echo) from executing. The fact that we see it print means that it DID execute. What the heck is going on?

And if execute the above all on one line, using || for conditional execution:

>t.cmd || echo %errorlevel%

>

This behavior is also the opposite of what I expect (altho it is consistent with the && behavior above).

Note that this weird behavior is ONLY true for bat files, but not for "raw commands".

Proof: consider the following command line interactions where instead of calling t.cmd, I try to execute the bogus command abcdef:

>abcdef
'abcdef' is not recognized as an internal or external command,
operable program or batch file.

>echo %errorlevel%
9009

>abcdef && echo %errorlevel%
'abcdef' is not recognized as an internal or external command,
operable program or batch file.

>abcdef || echo %errorlevel%
'abcdef' is not recognized as an internal or external command,
operable program or batch file.
9009

Here, && and || immediately see the exit code of the failed bogus command.

So why do cmd files behave differently?

A possibly related bug in cmd.exe has been observed in File redirection in Windows and %errorlevel%

Also, I am aware that ERRORLEVEL is not %ERRORLEVEL%

By the way, the code above was all executed on a Win 7 Pro 64 bit box. I have no idea how other versions of Windows behave.

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
HaroldFinch
  • 762
  • 1
  • 6
  • 17

3 Answers3

5

With t.bat slightly modified as follows:

@exit /b 123%~1
@echo If you see this, THEN EXIT FAILED..

Think out next output:

==>t.bat 1

==>echo %errorlevel%
1231

==>t.bat 2&echo %errorlevel%
1231

==>echo %errorlevel%
1232

==>cmd /V /C t.bat 3^&echo !errorlevel!
1233

==>echo %errorlevel%
0

==>cmd /V /C t.bat 4^&echo !errorlevel!^&exit /B !errorlevel!
1234

==>echo %errorlevel%
1234

==>

Resources

Edit to enlighten EnableDelayedExpansion:

==>cmd /v
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

==>t.bat 5&echo !errorlevel!
1235

==>echo %errorlevel%
1235

==>

Edit 2 to enlighten (or confuse?) && and ||. Save next code snippet as errlevels.cmd:

@ECHO ON >NUL
@SETLOCAL enableextensions enabledelayedexpansion
(call )
@echo ^(call ^) command clears errorlevel %errorlevel%
abcd /G>NUL 2>&1
@echo abcd /G: "'abcd' not recognized" errorlevel %errorlevel%
abcd /G>NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
@echo abcd /G: ^|^|   changed errorlevel %errorlevel%
find /G >NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
@echo find /G: ^|^| unchanged errorlevel %errorlevel%
call t.cmd 333 && echo YES !errorlevel! || echo NO !errorlevel!
type t.cmd
t.cmd 222 && echo YES !errorlevel! || echo NO !errorlevel!

Output (from errlevels.cmd):

==>errlevels.cmd

==>(call  )
(call ) command clears errorlevel 0

==>abcd /G 1>NUL 2>&1
abcd /G: "'abcd' not recognized" errorlevel 9009

==>abcd /G  1>NUL 2>&1  && echo YES !errorlevel!   || echo NO !errorlevel!
NO 1
abcd /G: ||   changed errorlevel 1

==>find /G   1>NUL 2>&1  && echo YES !errorlevel!   || echo NO !errorlevel!
NO 2
find /G: || unchanged errorlevel 2

==>call t.cmd 333   && echo YES !errorlevel!   || echo NO !errorlevel!
NO 333

==>type t.cmd
@exit /B %~1

==>t.cmd 222   && echo YES !errorlevel!   || echo NO !errorlevel!
YES 222

==>

Note that

  • || shows errorlevel 1 although 'abcd' not recognized error should be 9009 whilst
  • || keeps errorlevel 2 unchanged for FIND: Invalid switch error.
  • || branch evaluates in call t.cmd 333 whilst
  • && branch evaluates in t.cmd 222.

On (call ) see DBenham's answer:

If you want to force the errorlevel to 0, then you can use this totally non-intuitive, but very effective syntax: (call ). The space after call is critical.

If you want to set the errorlevel to 1, you can use (call). It is critical that there not be any space after call.

Community
  • 1
  • 1
JosefZ
  • 28,460
  • 5
  • 44
  • 83
  • JosefZ: that was a magnificent answer! – HaroldFinch Jul 02 '15 at 14:29
  • JosefZ: I am having a second look at your response, and I am a bit puzzled. I understand you totally up until your Edit2; delayed expansion (or my lack thereof) explains everything. What I do not understand are the first and fourth results in your list following "Note that". With the first one, do you have any explanation for why the 9009 errorlevel got displayed as 1? Your third result is exactly what I expect, but why in your fourth result does && evaluate? I thought that any errorlevel other than 0 from t.cmd should always cause the || to execute? – HaroldFinch Aug 26 '15 at 16:55
  • JosefZ: I opened a new DOS shell, and I can reproduce all your results exactly until Edit1. But your Edit2 results are more problematic. I started by opening another new DOS shell. I found that I had to execute "cmd /v" before executing any of your commands, else the !errorlevel! fails to resolve. Is that because your second command (the @SETLOCAL) is only supposed to be executed in a .cmd file? In fact, what is that "errlevels.cmd" at the start of your Output? Is it the name of a file that you want all those commands to be put in? – HaroldFinch Aug 26 '15 at 17:49
  • [Continuation of last comment...] Proceeding with pure command line interaction, when I execute the "find /G" line, I see 1 not 2. This holds for the next "@echo find /G" line as well. I tried putting all your commands into a new file named errlevels.cmd and executing that. It also prints 1 not 2 for both "find /G" lines. Bizarrely, for the final command, it prints "YES !errorlevel!". DOS is such craziness... – HaroldFinch Aug 26 '15 at 17:50
4

%errorlevel% is expanded when line is read. So it is 123 at the time the line was read so comes from the previous command not t.exe. && is executed only if the current errorlevel (from t command) is 0.

See setlocal /? and set /? for more info.

user5071892
  • 147
  • 2
1

@JosefZ's response provides excellent coverage of the disparity between ERRORLEVEL and exit code with respect to command scripts.

Unfortunately, as he pointed out, the && and || operators will only work if you invoke your command script with the call command. In most cases, you'd prefer that users can just run your command script, without having to remember to prefix it with a call each time.

Typically, I want my scripts to set both the ERRORLEVEL and the exit code to indicate failure (in order to have my scripts behave the same as regular executables). I used to use exit /b <nonzero> to try to do this, but had the problem indicated above.

It turns out that the Windows command interpreter exits with the exit code of the last command executed. The actual exit code of the exit /b <nonzero> command is, ironically, 0 (since it successfully exited). It does set the ERRORLEVEL, but not the exit code. Thus, that command won't work. The solution to all this is to (1) use the cmd /c exit <nonzero> command, and (2) to have it execute as the last command in the script. Since the cmd command returns back to the script, where the next command is then executed, the only way to get it to be the last line executed is to have it be the last line of your script.

Thus, here's a solution to get everything to behave as the OP requested:

@echo off & setlocal

if "%1" equ "fail" (
    echo -- Failure
    goto fail
) else (
    echo -- Success
    exit /b 0
)

:fail
    @REM // Exit with script a failure exit code.
    cmd /c exit 37
Steve Hollasch
  • 2,011
  • 1
  • 20
  • 18
  • Thank you for actually answering the question.  The idea that ``ERRORLEVEL`` and exit code are distinct (but related) is one that is *not* well documented (and I’m including JosefZ's answer, which I believe does *not* provide excellent coverage).  But see [dbenham’s answer here](https://stackoverflow.com/q/30714985/1672723#30717829 "to “How to properly report an exit status in batch?”"). – Scott - Слава Україні Feb 19 '20 at 01:18
  • It’s better to [edit] your answer.  Some people don’t look at comments; worse yet, comments sometimes get deleted. – Scott - Слава Україні Feb 17 '21 at 19:43