I think i have a basic understanding of ERRORLEVEL vs %ERRORLEVEL% but !ERRORLEVEL! confuses me.
I'm making a script that calls an executable, then tasklist to see if its running, then taskkill to kill it if it is and then trying to output the errorlevels and repeating for other exe's and i'm realising i really don't understand errorlevels in batch.
I set a variable equal to !errorlevel!
then used that variable without quotation marks in an echo, and the variable changed from one uint16 to another uint16 when there was an error after the set, like its a reference to the real one instead of a copy. I want copy. Can someone explain the difference between these guys?
Update:
Here is the snippet I'm working on.
for %%P in (%executableList%) do (
echo ----------------------------------------------------------------------------------
set exeErrorlevel=0
set running=false
start %%~fP
set exeErrorlevel=!ERRORLEVEL!
rem for debugging purposes
echo %%~nP%%~xP older errorlevel %ERRORLEVEL%
echo %%~nP%%~xP newer errorlevel !ERRORLEVEL!
echo before tasklist running var is : !running!
tasklist /FI "IMAGENAME eq %%~fP" | find /I /N /C "%%~fP" >nul && set running=true
echo after tasklist is running var is: !running!
if !running! equ true (
echo %%~nP%%~xP Program is running
taskkill /F /IM %%~nP%%~xP /T
echo %%~nP%%~xP Program was killed
if !exeErrorlevel! == 0 (
echo %passString% %%~nP%%~xP process was started and killed safely
echo %passString% %%~nP%%~xP process was started and killed safely >>%outputfile%
) else (
echo %failString% %%~nP%%~xP process was killed with errorcode !exeErrorlevel!
echo %failString% %%~nP%%~xP process was killed with errorcode !exeErrorlevel! >>%outputfile%
)
) else (
if !exeErrorlevel! == 0 (
echo %passString% %%~nP%%~xP process exited safely
echo %passString% %%~nP%%~xP process exited safely >>%outputfile%
) else (
taskkill /F /IM %%~nP%%~xP /T
echo %failString% %%~nP%%~xP process abruptly exited with errorcode !exeErrorlevel!
echo %failString% %%~nP%%~xP process abruptly exited with errorcode !exeErrorlevel! >>%outputfile%
)
)
echo. >>%outputfile%
)
I need to make sure exeErrorlevel has a copy of the errorlevel at a certain point in time - I only want to capture errors from the exe, not from the success/failure of tasklist/find/taskill. I'm concerned that exeerrorlevel, because of the delayed expansion, is accessing the delayed errorlevel upon execution. perhaps that should be set exeErrorlevel=%errorlevel% instead. In the line where i echo older and newer variables usually return different integers? In all my test runs %errorlevel% seems to typically return 0 whereas !errorlevel! is consistently non zero for executables with bad exit codes.
The errorlevel
errorlevel is the name of a dynamic variable (it is not placed in the environment block but hold in memory) that stores the exit code of the previous executed process/command (if it sets that value, read here, here, here and here).
The if command allows the usage of the if errorlevel n syntax to check if the value of the errorlevel variable is greater than or equal to n, without involving the batch parser into retrieving the value of the variable.
But, if we put the batch parser to work with variable values, %errorlevel% is just a reference to the value stored in the variable, a read operation. Just the same as !errorlevel!. The main difference between the two is when the value is retrieved depending on the rules on variable expansion.
There is a great difference in using the if errorlevel or retrieving the value in the variable:
The variable read operation will check if the environment block contains a variable with the indicated name.
The if constuct will not make this test.
If you do something like set errorlevel=10, the dynamic errorlevel value will not be retrieved with %errorlevel% or !errorlevel! as the value set in the environment will hide the dynamic value. But as if errorlevel does not read the environment block but directly reads the internal variable that holds the value, it will work without problems.
The variables
The batch syntax does not include the option of having more than one variable pointing to the same value in memory in a way that if one of the variables changes its value, the other will reflect the change.
This behaviour can be simulated by proper use of the different phases in variable expansion, properly setting a variable to the name of another and forcing the batch parser to do two passes over the command so first variable is resolved to the name of the second and that to the real value.
Your problem
Simplified (non even working) code just for analysis
1 for %%P in (%executableList%) do (
2
3 start %%~fP
4 set exeErrorlevel=!ERRORLEVEL!
5
6 echo %%~nP%%~xP older errorlevel %ERRORLEVEL%
7 echo %%~nP%%~xP newer errorlevel !ERRORLEVEL!
8 ....
9 if !running! equ true (
10 taskkill /F /IM %%~nP%%~xP /T
11 if !exeErrorlevel! == 0 (
12 ....
13 ) else (
14 echo process killed with errorcode !exeErrorlevel!
15 )
16 ) else (
17 if !exeErrorlevel! == 0 (
18 ....
19 ) else (
20 taskkill /F /IM %%~nP%%~xP /T
21 echo process abruptly exited with errorcode !exeErrorlevel!
22 )
23 )
line 1: the code in the do clause, all the code, is parsed. Any %var% variable read operation is removed from the code, replaced with the value inside the variable before starting the execution. This means that if the variable changes its value you will not be able to retrieve the changed value as the read operation does not exist, only the initial value in the variable.
line 3: the executable is launched, in a separate process, without waiting for the process to end. Is it important? See next line
line 4: the current (delayed expansion used) value of the errorlevel variable is retrieved and stored in exeErrorlevel variable. BUT the value stored is NOT the errorlevel returned by the executable (separate process, not waiting for it to end, how will we know what the exit code = errorlevel is?), but the exit code of the start command.
line 6: as the %errorlevel% read operation was removed, this line will echo the value that was stored in the errorlevel variable before the do clause started to execute.
line 7: the current value of the errorlevel variable is retrieved. And here, we can have a problem. How the script being executed is named? There is a difference between .bat and .cmd. On sucess the set command in line 4 will clear (set to 0) the errorlevel variable if this is a .cmd file, but will not change the errorlevel if it is a .bat file.
lines 11, 14, 21: as seen the exeErrorlevel variable does not contain a valid value. And no, changing the lines to !errorlevel! will not retrieve the exit code of the process, but the exit code of the taskkill.
To be able to retrieve the exit code / errorlevel of a process we need to wait for it to end. If you need to start the process, if it keeps running kill it, and in both cases retrieve the exit code, directly call the executable or use start "" /wait programName, AND run the killing process in parallel (ex. start /b "" monitor.bat programName or something similar before starting the program). The main process will wait and retrieve the exit code. The monitor process handles the killing.
Related
i am trying to run below snippet of code on my windows server.
#echo off
set BRANCH_NAME_ID=compiler_branch
if %BRANCH_NAME_ID%==compiler_branch ( echo INSIDE COMPILER BRANCH )
echo %BRANCH_SHORT_ID%|findstr /r "^[r][0-9][0-9]*_00$" & IF %ERRORLEVEL% == 0 ( echo IN IF ) ELSE ( echo INFO else)
pause
I was expecting the only output should be INSIDE COMPILER BRANCH because, BRANCH_NAME_ID variable is referring to compiler_branch. But some reason i am also getting IN IF as well.
Ouptut:-
INSIDE COMPILER BRANCH
IN IF
Press any key to continue . . .
As per the document https://ss64.com/nt/findstr.html i notice below and wrote the script accordingly. But some reason %ERRORLEVEL% is setting to 0 in line3 of my code thought the string is not matching :-
FINDSTR will set %ERRORLEVEL% as follows:
0 A match is found in at least one line of at least one file.
1 If a match is not found in any line of any file, (or if the file is not found at all).
2 Wrong syntax
An invalid switch will only print an error message in error stream.
Am i missing something ?
Because of how the interpreter reads files (see How does the Windows Command Interpreter (CMD.EXE) parse scripts? for a massive amount of info), %ERRORLEVEL% in that line gets replaced with its current value before the line is actually run. In order to have the command run and then have the value checked correctly, put the if statement on its own line.
#echo off
set BRANCH_NAME_ID=compiler_branch
if %BRANCH_NAME_ID%==compiler_branch ( echo INSIDE COMPILER BRANCH )
echo %BRANCH_SHORT_ID%|findstr /r "^[r][0-9][0-9]*_00$"
IF %ERRORLEVEL% == 0 ( echo IN IF ) ELSE ( echo INFO else)
pause
If for some reason you absolutely insist on using & to chain commands together (there is no reason to ever do this and it only makes things worse imo), then you can enable delayed expansion and use !ERRORLEVEL! instead.
#echo off
setlocal enabledelayedexpansion
set BRANCH_NAME_ID=compiler_branch
if %BRANCH_NAME_ID%==compiler_branch ( echo INSIDE COMPILER BRANCH )
echo %BRANCH_SHORT_ID%|findstr /r "^[r][0-9][0-9]*_00$" & IF !ERRORLEVEL! == 0 ( echo IN IF ) ELSE ( echo INFO else)
pause
I am trying to write the below code, and I want it to display the error before it exits, but whenever I run it, it exits right away.
#echo off
cd "haha"
if %errorlevel% 1 (
echo Failure reason given is %errorlevel%
sleep 5
exit /b %errorlevel%
)
echo files deleted successfully!
del /Q *
pause
I have also tried timeout /t 5 doesn't seem to work.
This should work :
#echo off
cd "haha" 2>nul
if %errorlevel%==1 (
echo Failure reason given is %errorlevel%
timeout /t 5
exit /b %errorlevel%
)
The correct syntax is
if errorlevel 1 (
meaning if errorlevel is 1 or greater
OR
if %errorlevel% geq 1 (
which means the same thing.
As it stands, cmd is interpreting you code as
if 1 1 (
(assuming errorlevel is 1). This is a syntax error, so cmd shows a message. If you are running by point-click and giggle, the window will close. If you are running from the prompt, then cmd will report a syntax error.
Be warned however that executing an exit statement from the prompt will abort the cmd instance. Better to use goto :eof unless you have good reason otherwise.
timeout /t 5 should work, but will generate a countdown.
timeout /t 5 >nul should appear simply to wait. The issue is that you have to solve the if syntax first, else the timeout instruction won't be reached.
I'm newbie in using batch on Windows and have a question about the use of errorlevel.
I referenced TechNet(Exit) and many examples on google.
Most of them used /b with %errorlevel% like this
if errorlevel 1 exit /b %errorlevel%
I wonder the difference between
if errorlevel 1 exit /b
and
if errorlevel 1 exit /b %errorlevel%
I think there are no difference because %errorlevel% is not changed. Am I wrong?
TL;DR
Most of the time there should be no difference, but technically exit /b %errorlevel% is strictly worse than exit /b if what you want is to exit without changing the error level.
Analysis
EXIT /B without the optional errorlevel parameter does not change the error level, so as a standalone command it is exactly equivalent to EXIT /B %errorlevel% provided that %errorlevel% resolves to the current error level.
But there are cases where it might not do so:
If an environment variable named ERRORLEVEL is defined then %errorlevel% always resolves to its value (which can be arbitrary), and not to the current error level.
If command extensions are disabled then %errorlevel% will never resolve to the current error level (it will still read the value of the environment variable with that name, if defined). You can verify this by starting a command prompt with CMD /E:OFF and trying ECHO %errorlevel%.
The current error level value as produced by %errorlevel% will be fixed at the time the command is parsed, not at the time execution reaches that expression. This can result in the wrong value being produced for more complex commands. Example:
copy j:\not_existing q:\not_existing & echo %errorlevel%
This will not produce the same result as
copy j:\not_existing q:\not_existing
echo %errorlevel%
because in the first case %errorlevel% will not produce the updated error level caused by the failed copy.
In a batch script that I am working on, a variable value is not being retained after calling 2 subroutines, one from another from a batch script's FOR loop.
Here is a code snippet demo'ing the scenario:
set ERRORCODE=0
FOR ... do (
call :myRoutine
#ECHO %ERRORCODE% // Here I am expecting the ERRORCODE to be a 1 (non-zero), but I am seeing that it is getting reset to 0
)
myRoutine:
call :another
IF %ERRORCODE% NEQ 0 GOTO :EOF // Here I am getting the ERRORCODE as 1 as expected
...
GOTO :EOF
another:
something went wrong here..
IF %ERRORLEVEL% NEQ 0 (
set ERRORCODE=1
GOTO :EOF
)
The ERRORCODE is retained just fine - you are just not accessing it properly.
Your problem is that %ERRORCODE% is expanded when the line is parsed, and the entire parenthesized block of code is parsed all at once, before the FOR loop is executed. So you are seeing the value that existed before the loop is executed.
The solution is to use delayed expansion, !ERRORCODE!, which requires setlocal enableDelayedExpansion. Delayed expansion occurs when the line is executed. Type HELP SET or SET /? from the command line for more information about delayed expansion.
set ERRORCODE=0
setlocal enableDelayedExpansion
FOR ... do (
call :myRoutine
echo !ERRORCODE!
)
... etc.
Intro
There's a lot of advice out there for dealing with return codes in batch files (using the ERROLEVEL mechanism), e.g.
Get error code from within a batch file
ERRORLEVEL inside IF
Some of the advice is to do if errorlevel 1 goto somethingbad, while others recommend using the
%ERRORLEVEL% variable and using ==, EQU, LSS, etc. There seem to be issues within IF statements and such, so then delayedexpansion is encouraged, but it seems to come with quirks of its own.
Question
What is a foolproof (i.e. robust, so it will work on nearly any system with nearly any return code) way to know if a bad (nonzero) code has been returned?
My attempt
For basic usage, the following seems to work ok to catch any nonzero return code:
if not errorlevel 0 (
echo error level was nonzero
)
Sorry, your attempt is not even close. if not errorlevel 0 is only true if errorlevel is negative.
If you know that errorlevel will never be negative, then
if errorlevel 1 (echo error level is greater than 0)
If you must allow for negative errorlevel, and are not within a parenthesized block of code, then
set "errorlevel=1"
set "errorlevel="
if %errorlevel% neq 0 (echo error level is non-zero)
Note - I edited my answer to explicitly clear any user defined errorlevel value after reading Joey's comment to the linked answer in the question. A user defined errorlevel can mask the dynamic value that we are trying to access. But this only works if your script has a .bat extension. Scripts with .cmd extension will set your ERRORLEVEL to 0 if you set or clear a variable! To make matters worse, XP will set ERRORLEVEL to 1 if you attempt to undefine a variable that does not exist. That is why I first explicitly define an ERRORLEVEL variable before I attempt to clear it!
If you are within a parenthesized block of code then you must use delayed expansion to get the current value
setlocal enableDelayedExpansion
(
SomeCommandThatMightGenerateAnError
set "errorlevel=1"
set "errorlevel="
if !errorlevel! neq 0 (echo error level is non-zero)
)
But sometimes you don't want delayed expansion enabled. All is not lost if you want to check the error level immediately after executing a command.
(
SomeCommandThatMightGenerateAnError && (echo Success, no error) || (echo There was an error)
)
If you absolutely must check the dynamic ERRORLEVEL value without using delayed expansion within a parenthesized block, then the following works. But it has the error handling code in two places.
(
SomeCommandThatMightGenerateAnError
if errorlevel 1 (echo errorlevel is non-zero) else if not errorlevel 0 (echo errorlevel is non-zero)
)
Here, at long last, is the "ultimate" test for non-zero errrolevel that should work under any circumstances :-)
(
SomeCommandThatMightGenerateAnError
set foundErr=1
if errorlevel 0 if not errorlevel 1 set "foundErr="
if defined foundErr echo errorlevel is non-zero
)
It can even be converted into a macro for ease of use:
set "ifErr=set foundErr=1&(if errorlevel 0 if not errorlevel 1 set foundErr=)&if defined foundErr"
(
SomeCommandThatMightGenerateAnError
%ifErr% echo errorlevel is non-zero
)
The macro supports parentheses and ELSE just fine:
%ifErr% (
echo errorlevel is non-zero
) else (
echo errorlevel is zero
)
One last issue:
Redirection of input and/or output can fail for any number of reasons. But redirection errors do not set the errorlevel unless the || operator is used. See File redirection in Windows and %errorlevel% for more information. So one can argue that there does not exist a fool-proof way to check for errors via errorlevel. The most reliable method (but still not infallible) is the || operator.