I have noticed what looks like a bug in Windows. I have a simple console application that returns -12. And a .cmd script that checks the return value using ERRORLEVEL and %ERRORLEVEL% NEQ 0. The script always works when the check is not nested inside an IF statement. When it is nested inside the IF statement, the return value detected by the script changes each time the script is ran. First time the script does not detect an error. The 2nd time, the script correctly detects error -12. On 3rd and subsequent runs, the script reports error 1.
Here is the script:
#echo off
IF "%1" NEQ "" (
ECHO RUNNING %1
%1
IF ERRORLEVEL 1 (
ECHO %1 returned positive value %ERRORLEVEL%
EXIT /B 1
)
IF %ERRORLEVEL% NEQ 0 (
ECHO %1 returned %ERRORLEVEL%
EXIT /B 1
)
)
You need setlocal enabledelayedexpansion.
#echo off
setlocal enableextensions enabledelayedexpansion
if "%1" NEQ "" (
echo running %1 %2 %3 %4 %5 %6 %7
%1 %2 %3 %4 %5 %6 %7
if errorlevel 1 (
echo %1 returned positive value !ERRORLEVEL!
exit /b 1
)
if !ERRORLEVEL! NEQ 0 (
echo %1 returned !ERRORLEVEL!
exit /b 1
)
)
endlocal
Related
I have two labels in my batch file. The initial label MAIN shall stay in control, so it Calls the second label, which ends with exit /b.
My script's Main label Calls the other, passing it arguments, which will be used to search strings wothin a text file.
When returning to the Calling label, it slways receives an empty return string.
I think this has something to do with the variable expansion in a loop. Who knows?
Here is the Script:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:MAIN
call :getReturnValue "1234 0815 4321 12815" "readBackVal"
if !errorlevel! equ 0 (
echo readback=!readBackVal!
echo readback=%readBackVal%
)
pause
exit /b 0
REM Function, which checks if the give return value is in a specific textfile (line for line check)
:getReturnValue
set "myExpectedValueList=%~1"
set "retval=%~2"
set "file=textexample.txt"
for %%i in (%myExpectedValueList%) do (
for /f "tokens=*" %%a in (%file%) do (
echo %%a|findstr /r "^.*%%i$"
)
if !errorlevel! equ 0 (
(endlocal
set /a "%retval%=%%i")
)
exit /b 0
)
)
exit /b 1
Here is the sample textfile textexample.txt:
Setup returns with errorcode=0815
Here is the answer i looked for:
Hi, first i want to inform that i made some changes due to the Answer of
#OJBakker. This changes are listed at the bottom of the script.
The problem was to return a value from a called function/label to the calling function/label. The stich here is, that the magic
is done in the (endlocal...) section of the called function/label -> means the return of the variable.
Before the endlocal command is executed, the compiler replaces the variables in this section by their values and afterwards executes the command´s from left to right. Means following:
First, the compiler sees following:
(endlocal
if "%retval%" neq "" (call set /a %retval%=%%i)
)
Second, the compiler replaces the variables by their values:
(endlocal
if "readBackVal" neq "" (set /a "readBackVal"=1815)
)
Third: This command is executed
(endlocal
if "readBackVal" neq "" (set /a "readBackVal"=1815)
)
Now here is my complete script (i also fixed some other problems with it which i commented at the bottom of the script
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:MAIN
setlocal
call :getReturnValue "1234 1815 4321 12815" "readBackVal"
if "!errorlevel!" equ "0" (
echo readback=!readBackVal!
)
pause
exit /b 0
REM Function, which checks if the give return value is in a specific textfile (line for line check)
:getReturnValue
setlocal
set "myExpectedValueList=%~1"
set "retval=%~2"
set "file=textexample.txt"
for %%i in (%myExpectedValueList%) do (
for /f "tokens=*" %%a in (%file%) do (
echo %%a|findstr /r "^.*%%i$" >NUL
)
if "!errorlevel!" equ "0" (
(endlocal
if "%retval%" neq "" (set /a %retval%=%%i)
)
exit /b 0
)
)
exit /b 1
REM Changes to initial posting:
REM Added "setlocal" keyword to the function "getReturnValue"
REM Corrected an invalid paranthesis in the (endlocal...) section
REM Changed the file "textexample.txt" -> 0815 to 1815 to remove leading zero (findstr. Problem),
REM Added check, if parameter "retval" has been passed to the called function e.g. is not empty
REM FINAL -> applied double variable expansion (call set /a ...) to return the value proper
REM to the :MAIN function.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:MAIN
call :getReturnValue "1234 0815 4321 12815" "readBackVal"
if %errorlevel% equ 0 (echo readback=%readBackVal%)
pause
endlocal
exit /b 0
REM Function, which checks if the give return value is in a specific textfile (line for line check)
:getReturnValue
set "myExpectedValueList=%~1"
set "retval=%~2"
set "file=textexample.txt"
for %%i in (%myExpectedValueList%) do (
for /f "tokens=*" %%a in (%file%) do (
echo %%a| >con 2>&1 findstr /r "^.*%%i$"
if !errorlevel! equ 0 (
set /a "%retval%=%%i"
exit /b 0
)
)
)
exit /b 1
rem changes:
rem endlocal moved to main.
rem check for errorlevel moved to within the commandblock of the inner for-loop.
rem 'exit /b 0' moved to within the if. This exit line stopped the for after the first item.
rem redirection added to findstr command. Now the output shows the remaining problem.
rem Invalid number. Numeric constants are either decimal (17), hexadecimal (0x11), or octal (021).
rem Findstr really does not like the value 0815, especially the starting zero.
rem I am not sure how to change the regexp so findstr won't barf at the leading zero.
rem Maybe someone else can solve this remaining problem.
I know that I can call another batch file using call path_to_other_batch_file.bat.
However, I don't know how can I call functions inside that file.
I have this batch file called Message.bat:
#echo off
EXIT /B %ERRORLEVEL%
:Error
echo [31m %* [0m
EXIT /B 0
:Warning
echo [33m %* [0m
EXIT /B 0
:Info
echo [34m %* [0m
EXIT /B 0
:Success
echo [32m %* [0m
EXIT /B 0
:Reset
echo [37m %* [0m
EXIT /B 0
And I want to use these functions in my other batch files so that I can simply write call:Error something went wrong without worrying about colors all the time.
I use it this way in Other.bat, but it does not work:
call C:\Infra\Message.bat
call:Error something went wrong
I receive this error:
The system cannot find the batch label specified - Error
So, how can I call those methods defined in my Message.bat file?
In your main batch(es)
call message error something went wrong
In message
#echo off
goto %1
:error
for /f "tokens=1*" %%L in ("%*") do echo %%M
exit /b 0
really not that hard...
or, better in message.bat
#echo off
FOR /f "tokens=1*" %%L IN ("%*") DO CALL :%%L %%M
EXIT /B %ERRORLEVEL%
:Error
echo [31m %* [0m
EXIT /B 0
:Warning
...
There are (at least) the two possibilities, one of which is courtesy of user jeb in this answer of him – so please give adequate credit to him by up-voting his post!
main.bat, establishing two calls of label :Label in sub.bat:
#echo off
echo/
echo ^>^>^> Supply `:Label` as the first argument:
call "%~dp0sub.bat" :Label arg1 arg2 arg3
echo ^>^>^> Returned to main script at this point.
echo/
echo ^>^>^> Embed `:Label` within the script path:
call "%~d0\:Label:\..%~p0sub.bat" arg1 arg2 arg3
echo ^>^>^> Returned to main script at this point.
exit /B
sub.bat, resolving label :Label in two distinct ways:
#echo off
echo Original path: "%~0"
echo Resolved path: "%~f0"
echo 1st argument : "%~1"
echo All arguments: %*
rem // Check whether first argument begins with (a) colon(s):
for /F "tokens=* delims=:" %%L in ("%~1") do if not "%%~L"=="%~1" goto :%%~L
rem // Check whether script path contains something between colons behind the drive:
for /F "tokens=3 delims=:" %%L in ("%~0") do goto :%%~L
rem // This code in the main section is never reached when a label has been provided.
exit /B
:Label
echo Function call: "%~f0" %*
exit /B
And this is the console output upon running main.bat:
>>> Supply `:Label` as the first argument:
Original path: "C:\LocalFiles\TiKi-ASIC\doc\work\TiCi-SV\Spec\sub.bat"
Resolved path: "C:\LocalFiles\TiKi-ASIC\doc\work\TiCi-SV\Spec\sub.bat"
1st argument : ":Label"
All arguments: :Label arg1 arg2 arg3
Function call: "C:\LocalFiles\TiKi-ASIC\doc\work\TiCi-SV\Spec\sub.bat" :Label arg1 arg2 arg3
>>> Returned to main script at this point.
>>> Embed `:Label` within the script path:
Original path: "C:\:Label:\..\LocalFiles\TiKi-ASIC\doc\work\TiCi-SV\Spec\sub.bat"
Resolved path: "C:\LocalFiles\TiKi-ASIC\doc\work\TiCi-SV\Spec\sub.bat"
1st argument : "arg1"
All arguments: arg1 arg2 arg3
Function call: "C:\LocalFiles\TiKi-ASIC\doc\work\TiCi-SV\Spec\sub.bat" arg1 arg2 arg3
>>> Returned to main script at this point.
As you may have noticed, in the first call, the label :Label is also part of the argument string %* which you have to pay specific attention to, though in the second call (applying said jeb's method), %* contains the pure argument string without an extra item.
Labels can sometimes become cluttered if you have multiple options. It is good for multiple tasks if some meets the criteria, but you seem to only want to change colors on events. So I will simply say do not use labels at all. The content of message.bat add:
#echo off
for /F %%a in ('echo prompt $E ^| cmd') do set "e=%%a"
if "%1" == "" exit /b 1
set "line=%*"
set "label=%1"
call set "rest=%%line:%label% =%%"
if /i "%1" == "Error" set "severity=[31m%rest%"
if /i "%1" == "Warning" set "severity=[33m%rest%"
if /i "%1" == "Info" set "severity=[34m%rest%"
if /i "%1" == "Success" set "severity=[32m%rest%"
if /i "%1" == "Reset" set "severity=[37m%rest%"
echo %e%%severity%%e%[0m
exit /b 0
To call this from another batch, simply do:
call messages.bat warning something went wrong
There is an odd chance that you want to add the actual severities into the message as well, and not just change the text color, then simply narrow it down to:
#echo off
for /F %%a in ('echo prompt $E ^| cmd') do set "e=%%a"
if "%1" == "" exit /b 1
if /i "%1" == "Error" set "severity=[31m%*"
if /i "%1" == "Warning" set "severity=[33m%*"
if /i "%1" == "Info" set "severity=[34m%*"
if /i "%1" == "Success" set "severity=[32m%*"
if /i "%1" == "Reset" set "severity=[37m%*"
echo %e%%severity%%e%[0m
exit /b 0
There's no built-in way to do that. call will either call an external file, an internal command or a label in the current file.
But, if you can change message.bat, you can make it take an additional argument, and call it. Then call will search in its own labels.
You'll have to take extra care to not pass the first argument to the label. For that, you can use the code from this answer:
#echo off
set "fn=%1"
shift
::"exit /b" is the same as "exit /b %errorlevel%"
if "%fn%"=="" exit /b
set "line=%1"
:loop
shift
if not "%1"=="" (
set "line=%line% %1"
goto :loop
)
call :%fn% %line%
exit /b
:Error
echo [31m %* [0m
EXIT /B 0
:Warning
echo [33m %* [0m
EXIT /B 0
:Info
echo [34m %* [0m
EXIT /B 0
:Success
echo [32m %* [0m
EXIT /B 0
:Reset
echo [37m %* [0m
EXIT /B 0
Then, you can call it like:
call message.bat error Something went wrong
#echo off
call :checkFTP1 %* > all_log_all_log_%date:~10,4%%date:~4,2%%date:~7,2%.log 2>&1
call :checkFTP2 %* >> all_log_all_log_%date:~10,4%%date:~4,2%%date:~7,2%.log 2>&1
call :checkFTP3 %* >> all_log_all_log_%date:~10,4%%date:~4,2%%date:~7,2%.log 2>&1
call :doCommands1 %* >> all_log_all_log_%date:~10,4%%date:~4,2%%date:~7,2%.log 2>&1
call :doCommands2 %* >> all_log_all_log_%date:~10,4%%date:~4,2%%date:~7,2%.log 2>&1
call :doCommands3 %* >> all_log_all_log_%date:~10,4%%date:~4,2%%date:~7,2%.log 2>&1
call :doCommands4 %* >> all_log_all_log_%date:~10,4%%date:~4,2%%date:~7,2%.log 2>&1
exit /b
:checkFTP1
#echo off
Setlocal
:: Is folder empty
set _TMP=
for /f "delims=" %%a in ('dir /b "C:\test\folder1"') do set _TMP="%%a"
IF {%_TMP%}=={} (
goto :Exit1
) ELSE (
goto :checkFTP2
)
Endlocal
:checkFTP2
#echo off
Setlocal
:: Is folder empty
set _TMP=
for /f "delims=" %%a in ('dir /b "C:\test\folder2"') do set _TMP="%%a"
IF {%_TMP%}=={} (
goto :Exit2
) ELSE (
goto :checkFTP3
)
Endlocal
:checkFTP3
#echo off
Setlocal
:: Is folder empty
set _TMP=
for /f "delims=" %%a in ('dir /b "C:\test\folder3"') do set _TMP="%%a"
IF {%_TMP%}=={} (
goto :Exit3
) ELSE (
goto :doCommands1
)
Endlocal
:doCommands1
call script1.bat
if %errorlevel% EQU 0 (goto :doCommands2 ) Else ( ECHO error on script 1 ,2,3,4)
exit
:doCommands2
call script2.bat
if %errorlevel% EQU 0 (goto :doCommands3 ) Else ( ECHO Script 1 Completed Successfully , ERRORS on 2,3,4)
exit
:doCommands3
call script3.bat
if %errorlevel% EQU 0 (goto :doCommands4) Else ( ECHO Script 2 Completed Successfully , ERRORS on 3,4)
exit
:doCommands4
call script4.bat
if %errorlevel% EQU 0 (goto :completed1) Else ( ECHO Script 3 Completed Successfully , ERRORS on 4)
exit
:Exit1
Echo Today Date %DATE% at %Time%
Echo ###################FTP-1 FILES MISSING #########################
Exit
:Exit2
Echo Today Date %DATE% at %Time%
Echo ###################FTP-2 FILES MISSING (#########################
Exit
:Exit3
Echo Today Date %DATE% at %Time%
Echo ###################FTP-3 FILES MISSING #########################
Exit
:completed1
Echo Today Date %DATE% at %Time%
Echo ###################all scripts Completed Successfully#########################
Exit
I have above batch file which calls multiple bat files. I have tested the script and it worked fine.
My only issue is that the log file generated contains all information, and it's a large file.
Is it possible to just log comments and echo, and exclude what executed in screen?
For example I don't want 1 file moved to be showing in log file.
I've taken your code and re-written it to use generic functions which deduplicates the code into an a more easily managed form and allows you to add or remove any steps to FTP and Script sections as needed by editing the list of variables.
I also only return output that does not include the "file(s) moved".
Alternatively if you really only want the status lines to print you could change this to just have those print to the log and not have all info print to the log (this is being done because you are putting the redirection to the log on the calls to other steps.
Also the way this was written before it was actually going through all the goto commands and not needing the calls to each at the top.
Here is the refactored code, I haven't tested it but it should be fine I have to step out for a few hours and you can ask any questions and I'll be happy to answer when I have time.
#( Setlocal EnableDelayedExpansion
echo off
SET "_FolderList="C:\test\folder1" "C:\test\folder1" "C:\test\folder1" "
SET "_ScriptList="c:\test\script1.bat" "E:\script2.bat" "C:\Path\To\script3.bat" "C:\Path\To\script4.bat" "
SET "_Scripts_Failed=1,2,3,4"
SET "_Scripts_Completed="
SET "_Continue=0"
CALL :GetDateTime
SET "MasterLog=all_log_all_log_!IsoDate!_!IsoTime!.log"
)
CALL :Main
( Endlocal
EXIT /B
)
:Main
SET /A "_Counter=0"
FOR %%_ IN (%_FolderList%) DO (
IF DEFINED _Continue (
SET /A "_Counter+=1"
CALL :CheckFTP_List %%~_
)
)>> "%MasterLog%"
SET /A "_Counter=0"
FOR %%_ IN (%_FolderList%) DO (
IF DEFINED _Continue (
SET /A "_Counter+=1"
CALL :DoCommands_List %%~_
)
)>> "%MasterLog%"
IF DEFINED _Continue (
Echo Today Date %DATE% at %Time%
Echo ###################all scripts Completed Successfully#########################
)
GOTO :EOF
:CheckFTP_List
REM Is folder empty
for /f "delims=" %%a in ('dir /b /A-D "%*') do (
set "_Continue=%%a" )
IF NOT Defined _Continue (
Echo.Today Date on %DATE% at %Time%
Echo.###################FTP-%_Counter% FILES MISSING #########################
)
GOTO :EOF
:DoCommands_List
call "*%" | FIND /I /V "file(s) moved" &REM only outputs lines that don't contain files moved.
if %errorlevel% NEQ 0 (
SET "_Continue="
SET "_Scripts_Completed=%_Scripts_Completed:,1=1%"
SET "_Scripts_Failed=!_Scripts_Failed:%_Scripts_Completed%=!"
Echo Today Date %DATE% at %Time%
ECHO. Error encountered on Script %Counter%! -- Completed Scripts: !_Scripts_Completed! -- Failed Scripts: !_Scripts_Failed!
) ELSE (
SET "_Scripts_Completed=%_Scripts_Completed:,1=1%,%Counter%"
)
GOTO :EOF
:GetDateTime
FOR /F "Tokens=1-7 delims=MTWFSmtwfsouehrandit:-\/. " %%A IN ("%DATE% %TIME: =0%") DO (
FOR /F "Tokens=2-4 Delims=(-)" %%a IN ('ECHO.^| DATE') DO (
SET "%%~a=%%~A"
SET "%%~b=%%~B"
SET "%%~c=%%~C"
SET "HH=%%~D"
SET "Mn=%%~E"
SET "SS=%%~F"
SET "Ms=%%~G"
)
)
SET "IsoTime=%HH%.%Mn%.%SS%.%Ms%"
SET "IsoDate=%yy%-%mm%-%dd%"
GOTO :EOF
I want to know error line number where error was occure by batch script and also i want to print whole line in batch script?
set t=%date%_%time%
set x=FORD_DLCM_T6_FTC
set a="%m%\%x%"
cd /d "C:\PRQA\PRQA-Framework-2.1.2\common\bin"
qacli admin --set-license-server 5055#00.00.0.0 || goto :error
qacli admin --debug-level DEBUG || goto :error
goto :EOF
:error
set remark= %ERRORLEVEL%
set status= Failure
echo ProjectName: %x%
echo Status: %status%
echo Remark: %remark%
echo Date: %t%
echo %x%,%status%,traceback(),%t% > "C:\Test\Build.csv"
echo Failed with error #%errorlevel%.
exit /b %errorlevel%
can any one help me out?
jeb has described how the search for the right line works. in the event of an error, a call is triggered and the parameters are a unique string and an offset, which points to the number of missing lines to the correct line. Findstr looks for the unmistakable string and prints that line number. With the settlement that's right again.
I've put together some macros to show that there are other ways to read the right line.
#echo off
setlocal
set prompt=$g$s
call :setAllmacros
(call)
rem begin in echo off - what line is it please ?
%ID-1:##==rem?%
(call )
%ID-1:##==call%
set/a+
%ID-1:##==001% call :ErrorLogging Line:%%L Errorlevel:%errorlevel%
echo 2
(call) || %ID-0:##==2% call :Errorlogging Line:%%L Errorlevel:%%errorlevel%%
echo %errorlevel%
find /n || %ID-0:##==003% call :Errorlogging Line:%%L Errorlevel:%%errorlevel%%
%ID-0:##==008%
pushG 556
%ID-1:##==004% call :ErrorLogging Line:%%L Errorlevel:%errorlevel%
%Line#print%:33=="
pause
exit /b
:errorlogging
#echo on
rem %*
#echo off
exit /b
:setAllmacros
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
(set ^"lf=^
%== define LF + CR ==%
)
for /f %%i in ('copy /z "%~f0" nul') do set "CR=%%i"
rem Prints the fullline itself with LineNumber into stderr
rem usage: %Line#print%:UniqueID"
rem command || %Line#print%:01"
set "Line#print= # <"%~f0" >&2 find /n "%%Line#print%%"
rem Read the fullline BEFORE into Variables with LineNumber; do something ...
rem usage: command 1 line before
rem %ID-1:##==01% [command %%L ... %%M ...]
set ID-1= if errorlevel 1 for /f "usebackQtokens=1*delims=:" %%L in (^
`cmd /v /c ^"findstr /n /r /c:"..*^!CR^!*^!LF^!.*ID-1:.#^=^##%%" "%~f0"^"`) do ^>^&2 echo BatchLine: %%L -- %%M ^&
rem Read the fullline itself into Variables with LineNumber; do something ...
rem usage: command || %ID-0:##==01% [command %%L ... %%M ...]
set ID-0= # for /f "tokens=1*delims=:" %%L in ('findstr /n /r /c:"ID-0:.#=##%%" "%~f0"') do ^>^&2 echo BatchLine: %%L -- %%M ^&
exit /b
hope it helps
Phil
echo The error level is: %ERRORLEVEL%
Produces
>The error level is: 15
What I would like:
>The error level is: F
do I need to do conversions or is there a way to display numbers differently?
Any help in the right direction is appreciated, thanks.
It was a long time ago, I was very bored.
cmdcalc.cmd
#echo off
if not defined trace set trace=rem
%trace% on
SetLocal
if "%1"=="/?" (
call :help %0
goto :eof
)
Set MinInBase=
if /i "%2" EQU "Bin" call :DoBin %1
if /i "%2" EQU "Hex" call :DoHex %1
If not defined BinStr call :DoDec %1
EndLocal & set RET=%RET%
goto :eof
:DoBin
Set MinInBase=2
Set ShiftBy=1
Set StartSyn=0b
call :DoCalc %1
goto :eof
:DoHex
Set MinInBase=16
Set ShiftBy=4
Set StartSyn=0x
call :DoCalc %1
goto :eof
:DoDec
if {%1} EQU {} goto :eof
set /a BinStr=%1
set RET=%BinStr%
echo %RET%
goto :eof
:DoCalc
Set BinStr=
SET /A A=%1
%Trace% %A%
:StartSplit
SET /A B="A>>%ShiftBy%"
%Trace% %B%
SET /A C="B<<%ShiftBy%"
%Trace% %C%
SET /A C=A-C
%Trace% %C%
call :StringIt %C%
If %B% LSS %MinInBase% goto :EndSplit
set A=%B%
goto :StartSplit
:EndSplit
call :StringIt %B%
set RET=%StartSyn%%BinStr%
Echo %RET%
EndLocal & set RET=%RET%
goto :eof
:StringIt
set Bin=0123456789ABCDEF
FOR /F "tokens=*" %%A in ('echo "%%BIN:~%1,1%%"') do set RET=%%A
set ret=%ret:"=%
Set BinStr=%Ret%%BinStr%
goto :eof
:help
echo %1 syntax:
echo.
echo %1 Calculation [Hex^|Bin]
echo.
echo eg %1 12*2 Hex
echo.
echo gives 0x18.
goto :eof
According to the external resource Windows Environment Variables, there is an undocumented built-in read-only variable =ExitCode which returns the current exit code in hexadecimal format. To ensure the ErrorLevel value equals the exit code, use cmd /C exit %ErrorLevel%.
So if you are using this line code...:
cmd /C exit %ErrorLevel%
echo The error level is: %=ExitCode%
...you will receive this (supposing the ErrorLevel is 15):
The error level is: 0000000F
To get rid of the leading zeros, use this...:
cmd /C exit %ErrorLevel%
for /F "tokens=* delims=0" %%Z in ("%=ExitCode%") do set "HEXCODE=%%Z"
if not defined HEXCODE set "HEXCODE=0"
echo The error level is: %HEXCODE%
...to get this:
The error level is: F
Just write it in vbscript instead of batch
put the vbscript statement below into a file.
WScript.Echo Hex( WScript.Arguments(0) )
then to run it, simple type this on the command line ( in a batch script, use a for loop to capture the value if required )
C:\workspace> cscript //nologo hex.vbs 15
F
There is no need to install anything. vbscript comes by default in most windows systems.
perl -e"printf qq{The errorlevel is: %X\n}, $ENV{ERRORLEVEL}"
Requires Perl to be installed, of course, but that's easy to do.