Batch Files - Error Handling - batch-file

I'm currently writing my first batch file for deploying an asp.net solution.
I've been Googling a bit for a general error handling approach and can't find anything really useful.
Basically if any thing goes wrong I want to stop and print out what went wrong.
Can anyone give me any pointers?

I generally find the conditional command concatenation operators much more convenient than ERRORLEVEL.
yourCommand && (
echo yourCommand was successful
) || (
echo yourCommand failed
)
There is one complication you should be aware of. The error branch will fire if the last command in the success branch raises an error.
yourCommand && (
someCommandThatMayFail
) || (
echo This will fire if yourCommand or someCommandThatMayFail raises an error
)
The fix is to insert a harmless command that is guaranteed to succeed at the end of the success branch. I like to use (call ), which does nothing except set the ERRORLEVEL to 0. There is a corollary (call) that does nothing except set the ERRORLEVEL to 1.
yourCommand && (
someCommandThatMayFail
(call )
) || (
echo This can only fire if yourCommand raises an error
)
See Foolproof way to check for nonzero (error) return code in windows batch file for examples of the intricacies needed when using ERRORLEVEL to detect errors.

Using ERRORLEVEL when it's available is the easiest option. However, if you're calling an external program to perform some task, and it doesn't return proper codes, you can pipe the output to 'find' and check the errorlevel from that.
c:\mypath\myexe.exe | find "ERROR" >nul2>nul
if not ERRORLEVEL 1 (
echo. Uh oh, something bad happened
exit /b 1
)
Or to give more info about what happened
c:\mypath\myexe.exe 2&1> myexe.log
find "Invalid File" "myexe.log" >nul2>nul && echo.Invalid File error in Myexe.exe && exit /b 1
find "Error 0x12345678" "myexe.log" >nul2>nul && echo.Myexe.exe was unable to contact server x && exit /b 1

Other than ERRORLEVEL, batch files have no error handling. You'd want to look at a more powerful scripting language. I've been moving code to PowerShell.
The ability to easily use .Net assemblies and methods was one of the major reasons I started with PowerShell. The improved error handling was another. The fact that Microsoft is now requiring all of its server programs (Exchange, SQL Server etc) to be PowerShell drivable was pure icing on the cake.
Right now, it looks like any time invested in learning and using PowerShell will be time well spent.

A successful ping on your local network can be trapped using ERRORLEVEL.
#ECHO OFF
PING 10.0.0.123
IF ERRORLEVEL 1 GOTO NOT-THERE
ECHO IP ADDRESS EXISTS
PAUSE
EXIT
:NOT-THERE
ECHO IP ADDRESS NOT NOT EXIST
PAUSE
EXIT

I guess this feature was added since the OP but for future reference errors that would output in the command window can be redirected to a file independent of the standard output
command 1> file - Write the standard output of command to file
command 2> file - Write the standard error of command to file

Python Unittest, Bat process Error Codes:
if __name__ == "__main__":
test_suite = unittest.TestSuite()
test_suite.addTest(RunTestCases("test_aggregationCount_001"))
runner = unittest.TextTestRunner()
result = runner.run(test_suite)
# result = unittest.TextTestRunner().run(test_suite)
if result.wasSuccessful():
print("############### Test Successful! ###############")
sys.exit(1)
else:
print("############### Test Failed! ###############")
sys.exit()
Bat codes:
#echo off
for /l %%a in (1,1,2) do (
testcase_test.py && (
echo Error found. Waiting here...
pause
) || (
echo This time of test is ok.
)
)

Its extremely easy!
Create a file that contains:
call <filename> // the file you made
cls
echo An error occured!
<Your commands>
pause
So now when you start it, it will launch your program as normal. But when anything goes wrong it exits and continues the script inside the first file. Now there you can put your own commands in.

Related

Hiding error messages if writing to a file fails in a Windows .BAT script

i just subscribe here.
I would want to hide the "Acces is denied." error message in case writing to a read-only file fails. Could someone tell me why the following code doesn't work as expected ? I'm using Windows 10.
:: This file is assumed to be read-only.
echo( >"MyReadOnlyFile.txt" 2>nul
( echo( ) >"MyReadOnlyFile.txt" 2>nul
In both case the "Access is denied." message is displayed, although the following "How-to: Redirection" page at SS64 says the contrary: https://ss64.com/nt/syntax-redirection.html
Redirect to NUL (hide errors)
command >filename 2> nul Redirect output to file but suppress error
(command)>filename 2> nul Redirect output to file but suppress CMD.exe errors
Thanks in advance for your help.
Lionel.
As stated already in the comments, the Access is denied message is output to stdOut, as opposed to the expected stdErr.
Since your task is related to a read only issue, (there are other reasons why that message may be output), I'd offer this relatively simple alternative:
#Dir "MyReadOnlyFile.txt" /A:-DR /B 1>NUL 2>&1 || (Echo() 1>>"MyReadOnlyFile.txt"
It simply determines if the existing file is read only, and only performs a write operation if it is not.
Alternatively you could use the same method to reset the read only attribute, write to the file, then apply the read only attribute again:
#Set "File=MyReadOnlyFile.txt"
#Dir "%File%" /A:-DR /B 1>NUL 2>&1 && (
%SystemRoot%\System32\attrib.exe -R "%File%" 2>NUL
If Not ErrorLevel 1 (
(Echo() 1>>"%File%"
%SystemRoot%\System32\attrib.exe +R "%File%"
)
) || (Echo() 1>>"%File%"
Thanks for your quick answers, i really appreciate.
Microsoft has been telling us for over a decade that PowerShell is the future of scripting.
I know indeed. I'm currently optimizing my own application in Batch+third party tools (a retrogame launcher, the main Batch script is about 41000 lines) by improving various stuffs here and there, so i guess Powershell will wait for a next project (smile). But anyway thanks for your advice, you're right.
Alternatively you could use the same method to reset the read only attribute, write to the file, then apply the read only attribute again:
This dir /A:-DR /B is nice and fast way to test if a file is read-only, indeed. There may be benefits to proceed in this way instead of 1. grabbing the file attribute with a more conventional for %%F in ("MyReadOnlyFile.txt") do set "file.attr=%%~aF" then 2. testing the second character by enabling delayed expansion. I will check where i can use it in my code to optimize some parts slightly, thank you for your suggestion Compo.
The error isn't written to STDERR, but to STDOUT (don't ask me, why), so echo( >"MyReadOnlyFile.txt" 1>nul suppresses the error message (sadly along with any possible useful output)
Thanks for your answer Stephan. Well, we know that Batch is quite insane often, but a such behavior would be more than insanity (smile). Redirecting stdout 1>nul doesn't hide the error message (as expected). However, surrounding the command and file redirection with brackets works as expected:
(echo( >"MyReadOnlyFile.txt") 2>nul
It might be a possible typing error on SS64 about the position of surrounding brackets, it should be (command >"filename") 2>nul. The error message is redidrected to stderr as expected, confirmed by the following if you open the "stderr.txt" file.
(echo( >"MyReadOnlyFile.txt") 2>"stderr.txt"
Again thank you to have replied, i did read a ton of threads on stackoverflow during the whole development of my application but i never subscribed. So that's a special feeling to have opened an account finally.
EDIT
I also add these two in case it can help someone.
rem Merge stderr to stdout.
set "_mute=>nul 2>&1" & set "_mute1=1>nul" & set "_mute2=2>nul"
rem Force ERRORLEVEL to 0.
set "_errlev0=(call )"
rem If you're lazy to type it each time (i am !).
set "_ede=enabledelayedexpansion"
rem Existing file name.
set "file.path=MyTestFile.txt"
rem Paths to the SQLite executable and SQLite database (example).
set "sqlite_file.path=C:\SQLite\SQLite.exe"
set "db_file.path=C:\Database\MyBase.db"
setlocal %_ede%
rem --- Test1 ---
%_errlev0%
echo ERRORLEVEL=!ERRORLEVEL!
(echo| set /p dummy_name="Hello & world">>"!file.path!") %_mute% && (
echo Test1 succeeded
) || (
echo Test1 failed
)
echo %ERRORLEVEL=!ERRORLEVEL!
rem --- Test2 ---
:: Whatever SQLite query of your choice.
set "sql_query=SELECT arcade_t.description FROM arcade_t WHERE arcade_t.description LIKE '%%Capcom%%'"
set "sql_bad_query=_!sql_query!"
%_errlev0%
echo ERRORLEVEL=!ERRORLEVEL!
(>>"!file.path!" "!sqlite_file.path!" "!db_file.path!" "!sql_query!") %_mute% && (
echo Test2 succeeded
) || (
echo Test2 failed
)
echo ERRORLEVEL=!ERRORLEVEL!
endlocal
Surrounding the WHOLE command echo| set /p dummy_name="..." with brackets (as mentionned above, cf typing error in the SS64 page) and muting both stdout+stderr (or stderr only with %_mute2%) will hide the error message if writing the file failed.
Mentionning a variable name in the command set /p dummy_name="..." sets ERRORLEVEL as expected once the command echo| set /p="..." ends: 0 if writing the file succeeded, 1 if it failed. On the contrary, using a nameless command echo| set /p ="..." sets ERRORLEVEL to 1 even if writing the file succeeded, so should be avoided.
Again, surrounding the WHOLE SQLite command with brackets will hide the "Acces is denied." message but the SQLite "Error: near "_SELECT": syntax error" error message as well in case the SQLite query is syntaxically incorrect. This has has been tested and confirmed by replacing the proper query with the bad one sql_bad_query.
This will work in a batch-file run by cmd. If you are on a supported Windows system, PowerShell is available. Microsoft has been telling us for over a decade that PowerShell is the future of scripting.
If the file is not read-only or does not exist, this will write to it.
powershell -NoLogo -NoProfile -Command ^
if (-not (Get-ChildItem -Path .\rofile.txt -ErrorAction SilentlyContinue).IsReadOnly) ^
{ Set-Content -Path '.\rofile.txt' -Value 'then' }
Of course, it is easier and less cryptic if it is written as a PowerShell .ps1 file script.
if (-not (Get-ChildItem -Path .\rofile.txt -ErrorAction SilentlyContinue).IsReadOnly)
{ Set-Content -Path '.\rofile.txt' -Value 'then' }
You need to enclose the whole redirection expression in parantheses, otherwise you just redirect the output of the echo command:
(echo/> "MyReadOnlyFile.txt") 2> nul
Anyway, I'd use something that does not alter the file contents just in case it's not write-protected:
(rem/>>"MyReadOnlyFile.txt") 2> nul
I complete answers above by adding the following one that could be a fast workaround depending your needs.
setlocal disabledelayedexpansion
set "_mute=>nul & 2>&1"
rem * Set the backspace character (cf other threads in this forum).
for /F "usebackq" %%c in (`"prompt $H & for %%a in (1) do rem"`) do set "CHR8=%%c"
rem * Display a string containing redirection characters.
rem - The leading+ending double quotes will be overriden, so output will be `Hello & | < > world`
rem - WARNING: An ending space must be typed after the last `%CHR8%`, ie the typed command is `echo "%CHR8Hello & | < > world"%CHR8% `.
rem - It's way faster than using `echo| set /p dummy="Hello & | < > world".
rem - ERRORLEVEL is set properly, so conditional execution `&& (..) || (..)` can be used.
echo "%CHR8Hello & | < > world"%CHR8%
rem * The string can be written to a file as well.
(>> "%MyFile.path%" (
echo BEG-OF-TEST
echo "%CHR8Hello & | < > world"%CHR8%
echo END-OF-TEST
)) %_mute% && (
rem Tasks in case of successfull write.
) || (
rem Tasks in case of write failure.
)
Screenshots 1+2 in action (taken from my application), ampersands & are properly written to a file for the MAME game "HyperMan".
Initially each line was written piece by piece by using a slower <nul set /p =".." (or <nul set /p dummy="..") without the need to open a temporary local scope at each loop iteration, but these two commands set ERRORLEVEL to 1 in case of success (as discussed elsewhere on this forum) so can't be use in a reliable way. All in all i couldn't use my exception handler to detect a write failure efficiently in this (important) function, hence my initial post about hiding and capturing error with echo| set /p dummy="..".
If for example i change the attributes of the file in_mch_x.lst_file.path to read-only and i retry the same search, then the write failure is now properly captured thanks to the standard redirection of echo (screenshot 3 below) and the exception code properly propagated to highler calling levels. Screenshot 4 is the same in debug mode.
Again thanks a lot guys, i should have subscribed here earlier for sure ! :p

Why batch sometimes does not process commands like CMD does and gives unexpected and different results [duplicate]

This question already has an answer here:
wrong value of %errorlevel% in nested if in .bat file
(1 answer)
Closed 2 years ago.
When I run a command from the CMD directly it works perfectly fine but when run the same command in a batch file (or Jenkins using batch) it gives me a different result and I dont understand why.
To explain it simple, I'm running command below to search for a literal string within a log file:
findstr /C:"MY STRING WITH A %variable%" M:\Logs\output.log
If I check the %ERRORLEVEL% of the result it shows the expected values (0 = found the string or 1 = didnt find it)
However, when I run the same line from a batch file, even from Jenkins, the result is always 0, even though the string is not present in the log, the %ERRORLEVEL% is always 0.
This is the portion of the batch file which includes the command:
if %COUNTER% ==1 (
if not exist M:\Logs\current_bak (
ROBOCOPY "M:\Logs\tkcurrent" "M:\Logs\tkcurrent_bak"
REN "M:\Logs\current" "M:\Logs\current_bak"
MKDIR M:\Logs\current
echo Folders have been backed up
) else (
echo Back up folders are already in place )
findstr /C:"MY STRING WITH A %variable%" M:\Logs\output.log
if %ERRORLEVEL% == 0 (
echo Process has already being kicked off for the files with date %YYMMDD%, skipping it...
echo Download and backup compleated
exit /b 0 )
else (
echo Triggering next Jenkins Job:
curl -I http://<user>:<token>#remoteserver.domain.com:<port>/job/Hello_World/build?token=hello_world
exit /b 0 )
)
Has someone experienced this in the past or can guide me better on what I'm doing wrong or not understanding?
Thanks a lot!
Your error is a basic syntactical error that I've made plenty of times as well :P. The issue is in your else statement which is a peculiar issue, but what happens is that the else only will get processed if it is on the same line as the if statement.
PROOF OF CONCEPT
If I have the if statement and else on different lines like so:
#echo off
echo test
if errorlevel 1 (echo here)
else (echo not there)
pause
This will have the error:
'else' is not recognized as an internal or external command,
operable program or batch file.
However if I change the code a tiny bit so that the else is on the same line as the if, like so:
#echo off
echo test
if errorlevel 1 (echo here
) else (echo not there)
pause
It will not have an error and output
test
not there
Your code done correctly would be:
if %COUNTER%==1 (
if not exist M:\Logs\current_bak (
ROBOCOPY "M:\Logs\tkcurrent" "M:\Logs\tkcurrent_bak"
REN "M:\Logs\current" "M:\Logs\current_bak"
MKDIR M:\Logs\current
echo Folders have been backed up
) else (
echo Back up folders are already in place )
findstr /C:"MY STRING WITH A %variable%" M:\Logs\output.log
if %ERRORLEVEL% == 0 (
echo Process has already being kicked off for the files with date %YYMMDD%, skipping it...
echo Download and backup compleated
exit /b 0
) else (
echo Triggering next Jenkins Job:
curl -I http://<user>:<token>#remoteserver.domain.com:<port>/job/Hello_World/build?token=hello_world
exit /b 0 )
)
For more information on this type in if /? into cmd.
Note: I also fixed I believe a typo in your code of findsrt instead of findstr

Compare text from two files and do something if they are diffrent

So I need to compare two text files and if there is a difference in content in one of them then tell the batch file to GOTO Diffrence I know that the FC command can check diffrences but can I use it to make it goto a diffrent place
so I run
fc %cd%\ActiveVer.txt %cd%\currentver.txt
ActiveVer.txt says:
0.5.6
and currentver.txt says:
0.5.7
fc tells me the difference.
But I'm trying to see and make it run GOTO out-of-date if there is a difference and do echo You are up to date! if there is none.
Should I run another command to do this or is there something that allows me to do that with fc?
Most commands return an error code upon completion. By convention, zero equates to success, and non-zero equates to failure (this is a general rule - there are exceptions). So most of this answer can be applied to any command, as long as you know how to interpret the returned error code.
The FC command returns 0 if the files match, and 1 it there is at least one difference. You don't need to see the output of the command (the differences), so you can redirect stdout to nul.
One option is to use IF ERRORLEVEL N, which evaluates to true if the returned error code is >= N.
fc ActiveVer.txt CurrentVer.txt >nul
if errorlevel 1 goto outOfDate
echo you are Up-To-Date
exit /b
:outOfDate
echo you are Out-Of-Date
exit /b
Note that %cd%\file and file are equivalent - the %cd% is not needed.
Another option is to check for a specific value by using the dynamic %ERRORLEVEL% "variable".
fc ActiveVer.txt CurrentVer.txt >nul
if %errorlevel%==1 goto outOfDate
echo you are Up-To-Date
exit /b
:outOfDate
echo you are Out-Of-Date
exit /b
But I almost never use either syntax above. Instead I use the conditional command concatenation operators && and ||. Commands after && only execute if the prior command returned zero, and commands after || execute if the command returned non-zero. Note that commands after && might fail, which could cause the || commands to fire, even if the original command succeeded. For this reason, it is a good idea to end your && commands with a command that is guaranteed to succeed. A good choice is (call ), which does nothing other than return 0 (success).
someCommand && (
REM Success commands go here
REM Make sure the last commmand in this block returns 0
(call )
) || (
REM Error commands go here
)
You simply want to GOTO if FC "fails" (finds a difference), so you only need the ||.
fc ActiveVer.txt CurrentVer.txt >nul || goto outOfDate
echo You are Up-To-Date
exit /b
:outOfDate
echo You are Out-Of-Date

How to prevent MSBuild from aborting on signtool errorlevel <> 0

In our current setup build process (WiX 3.9), certain files get digitally signed. To avoid signing an unchange file again, I want to check each file for a signature and skip signing if it's already signed.
I've tried using signtool.exe verify /pa filename.exe to check if the file is already signed, and signtool returns with a nonzero ERRORLEVEL if there is no signature. I thought I could check the error code after the call and handle it appropriately:
signtool.exe verify /pa %1
IF ERRORLEVEL 0 goto already_signed
rem Sign file now
[...]
goto finished
:already_signed
echo File %1 is already signed, skipping
:finished
This works fine if a signature is found and signtool returns 0. But if no signature is found, resulting in a nonzero ERRORLEVEL, MSBuild takes immediate notice of that and displays an error message: EXEC : SignTool error : No signature found. One step later, the build fails due to a -1 return code from the signing batch file. In terms of the build process however, there were no errors that would have to be treated like ones.
I've already tried to reset the ERRORLEVEL to 0 after the signtool verify call, but that doesn't work. Any ideas?
As S.T. suggests above, simply add exit /b 0 below your :finished label. If you want to reset %ERRORLEVEL% without exiting your script, you can do cmd /c exit /b 0, then %ERRORLEVEL% will be reset to zero and your script will continue.
Just so I feel like I put some effort into this answer, I'll offer an unrelated tip. :)
Another neat trick for testing exit code status is conditional execution.
>NUL 2>&1 signtool.exe verify /pa "%~1" && (
echo File %1 is already signed, skipping
) || (
rem Sign file now
[...]
exit /b 0
)
The >NUL 2>&1 stuff simply hides all stdout and stderr output of signtool.exe. The code block after && fires if signtool exits 0. Otherwise, the code block after || fires.

Why is only the first line of the condition executed?

I have a simple batch file in which I want to do things if an operation failed. In the condition, it seems only the first lines executes for some reason...
call "%local_path%\unins000.exe" /verysilent
IF ERRORLEVEL 1 (
echo ERROR: uninstallation failed
REM Installation failed, deletes the folder
rmdir /s /q "%local_path%"
set batcherrorlevel=1
)
IF %batcherrorlevel% neq 0 exit /b %batcherrorlevel%
If the uninstall fails, the echo works and displays, but my exit code at the end is 0. However, if I place the line "set batcherrorlevel=1" to be the first line in the condition, the exit code is 1 but the echo does not print.
EDIT: I never found the real cause of the issue, but it seems it solved iself... Bothers me a little, but as long as it works, I guess it's ok...
Sometimes batch file crashes when you put remarks inside a block statement
I think you may need to wrap it in percents, and add an == Operator.
I string-ify my comparisons, but that may just be me.
If "%ERRORLEVEL%"=="1"
Or you could look at if using a line continuation character is needed.
See http://blogs.msdn.com/b/oldnewthing/archive/2008/08/06/8835317.aspx

Resources