Very interesting topic, but you haven't solved anything - you fooled yourself :-)
I run a slight variation of your code, and things no longer work like you are expecting!
@echo off
:: clear any existing error
call;
echo %errorlevel%
(
echo Blah
ehco Blah & rem Intentional mistake
echo Blah
) 2>nul||echo One of the commands failed.
echo %errorlevel%
echo(
echo --------------------------
echo(
echo %errorlevel%
call :block 2>nul||echo One of the commands failed.
echo %errorlevel%
exit /b
:block
echo Blah
echo Blah
echo Blah
exit /b
--- output ---
0
Blah
Blah
9009
--------------------------
9009
Blah
Blah
Blah
One of the commands failed.
9009
Normally, the command after || only fires if the prior command failed. The || operator normally does not read the %ERRORLEVEL% variable directly, it usually detects the error status of the prior command directly.
In the first set of code, one of the commands failed, and the ERRORLEVEL was set to 9009. But the last command in the block succeeded, so the conditional failure message does not fire.
The second block of code uses a CALL. When CALL is executed, the current %ERRORLEVEL% at time of return is normally used as the return state for the CALL command. The ERRORLEVEL was 9009 upon routine entry, and none of the commands within the routine clear the error, so 9009 is returned as the error status for the CALL statement. The return code can be forced to any value by specifying the errorlevel as an additional parameter after EXIT /B. For example, exit /b 0.
It can get a bit tricky trying to figure out which commands always set the ERRORLEVEL, and which commands only set the ERRORLEVEL if there was an error. I believe internal commands only set ERRORLEVEL when there is an error, and do not clear any prior ERRORLEVEL if the command succeeded. (I'm assuming .BAT extension. I believe .CMD extension changes the behavior)
Most (all?) external commands always set the ERRORLEVEL. It is up to the executable, but most external commands return 0 for success and non-zero for failure.
Since a block of code may contain a mixture of internal and external commands, you cannot assume the final ERRORLEVEL will be non-zero if any of the commands failed. An intervening external command may have cleared the ERRORLEVEL. You should check the return code of each command and set your error variable to preserve the existence of an error within your block. You can then explicitly return the error code you want with EXIT /B.
The only way I can think to only print stdout when all commands succeed is to redirect all stdout to a temporary file, and keep tabs as to whether any command fails. At block end, you can TYPE the temp file contents if and only if there was no error. Then delete the temp file regardless.
blahs and no error message. What are you referring to here?