I've got a batch file that does several things. If one of them fails, I want to exit the whole program. For example:
#echo off
type foo.txt 2>> error.txt >> success.txt
mkdir bob
If the file foo.txt isn't found then I want the stderr message appended to the error.txt file, else the contents of foo.txt is appended to success.txt. Basically, if the type command returns a stderr then I want the batch file to exit and not create a new directory. How can you tell if an error occurred and decide if you need to continue to the next command or not?
use ERRORLEVEL to check the exit code of the previous command:
if ERRORLEVEL 1 exit /b
EDIT: documentation says "condition is true if the exit code of the last command is EQUAL or GREATER than X" (you can check this with if /?). aside from this, you could also check if the file exists with
if exist foo.txt echo yada yada
to execute multple commands if the condition is true:
if ERRORLEVEL 1 ( echo error in previous command & exit /b )
or
if ERRORLEVEL 1 (
echo error in previous command
exit /b
)
Related
I am writing a batch script to extract data from a server. If any command fails in the script below, I want to exit the batch program and record the error in an error log file.
Here is what I have so far:
call epmautomate login aaaa#a.com abcd123 https://www.website.com
epmautomate exportdata ABC_DATA_EXTRACT & epmautomate downloadfile ABC_DATA_EXTRACT.zip & MOVE "C:\doc\bin\ABC_DATA_EXTRACT.zip" C:\Users\Administrator\Documents\folder & cd C:\Users\Administrator\Documents\folder & unzip -n C:\Users\Administrator\Documents\folder\ABC_DATA_EXTRACT.zip & C:\Users\Administrator\Documents\folder\change_header.cmd
how can I add error detection and logging?
Before I discuss the logging and the errors I would like to point out what the reason was we wanted you to clean your code (you didn't do it as how we suggested):
there is no reason why you should use the & between your commands. Put each command on a separate line. & means "whatever happens execute also the following" but makes scripts unreadable. So if possible always put each command on a seperate line instead.
you use different ways to execute epmautomate. If it is a script use call each time you want to execute it else don't use call at all when you wish to execute epmautomate. Supposing your code works, I can assume it is not a script and the call isn't needed.
it is preferable to surround all your paths with double qoutes
Now the logging and error detection.
I know 2 different approaches for logging errors and making a batch-file exit on an error. The most important condition is that all commands (either personnal, built-in or 3rd party scripts/software) you use in your script must set the errorlevel correctly. That is the only condition if you want the cmd interpreter to know that an error occured during the execution of a command. Another condition this time for a proper log-file is that the commands you use should write proper error messages. In both approaches the error messages written to the error channel are appended to the log-file using the 2>> error redirection operator (the link also shows how to redirect both output and error messages if interested). I'll assume the path to the log-file without surrounding double quotes is available in the logfile variable. I've added the double quotes each time the variable is used: "%logfile%".
The first approach makes use of an IF statement. The IF ERRORLEVEL n will check if the errorlevel is greater or equal to n. So if we assume a command command1 sets errorlevels correctly (0 if successful, 1 or greater otherwise), the following should be able to stop your script if an errorlevel occured during its execution
command1 2>> "%logfile%"
IF ERRORLEVEL 1 (
REM some extra personal errormessage if needed
echo command1 failed, check the log-file for more info
REM Exit current script and set errorlevel on 1 (failure)
exit /b 1
)
You can exit with another strict positive integer if you want and use personal error codes.
The second approach makes use of conditional execution execution inside code blocks (groups of commands). command1 && command2 will execute command2 only if command1 was succesful. The errorlevel after command1 && command2 will reveal if both exited successfully or if one of them exited with an error. If you group commands together between ( ) like this:
(
command1
command2
command3
)
the cmd interpreter will just put all commands on one line and put && in between: it will end up executing command1 && command2 && command3. So to execute a group of commands and exit if an error occured during execution of one of the commands, one can use
(
command1
command2
command3
) 2>> "%logfile%"
IF ERRORLEVEL 1 (
REM some extra personal errormessage if needed
echo An error occured, consult the log-file for more info
REM Exit current script and set errorlevel on 1 (failure)
exit /b 1
)
There is one downside for this approach: the use of variables inside the code blocks is limited. As all commands inside the block are parsed and executed as one single command, you cannot give a variable a new value inside the block and use that new value inside the same block with the normal variable expansion (i.e. %var%). You'll have to use delayed expansion if you want to be able to use the new value.
Which approach you pick depends on the situation and on what you want to achieve. The first approach is a more general one. Thanks to the "high" variety of IF statements, it can be used for commands that don't set the errorlevel but use another way of communicating an error that occurred during execution. The first approach also allows a more accurate error analysis because you know which command caused the error and can add that info in the log-file easily. There is a problem though: you'll have some serious type work. You can try to solve that issue by using a function that executes all your commands and exit the batch script from within the function if needed but it's not that easy. I have another more easy option to abandon the script when using the function but I'll come back to it later.
The second approach has the disadvantage that you can't easily identify the command where the error occurred. You can solve that issue by printing a "success" message after each command to the log-file. After all, a good log-file should also contain what has been executed successfully.
#echo off
set logfile=C:\Users\path\to\logfile errors.txt
(
epmautomate login aaaa#a.com abcd123 https://www.website.com 2>> "%logfile%"
echo first epmautomate ok >> "%logfile%"
epmautomate exportdata ABC_DATA_EXTRACT 2>> "%logfile%"
echo second epmautomate ok >> "%logfile%"
epmautomate downloadfile ABC_DATA_EXTRACT.zip 2>> "%logfile%"
echo third epmautomate ok >> "%logfile%"
MOVE "C:\doc\bin\ABC_DATA_EXTRACT.zip" "C:\Users\Administrator\Documents\folder" 2>> "%logfile%"
echo move zip ok >> "%logfile%"
cd "C:\Users\Administrator\Documents\folder" 2>> "%logfile%"
echo cd to admin folder ok >> "%logfile%"
unzip -n "C:\Users\Administrator\Documents\folder\ABC_DATA_EXTRACT.zip" 2>> "%logfile%"
echo unzip zipfike ok >> "%logfile%"
call "C:\Users\Administrator\Documents\folder\change_header.cmd" 2>> "%logfile%"
echo call to change-header ok >> "%logfile%"
)
IF ERRORLEVEL 1 (
REM some extra personal errormessage if needed
echo An error occured, consult the log-file for more info
REM Exit current script and set errorlevel on 1 (failure)
exit /b 1
)
It does the job. You'll just have to write each thing you want in your log and move the error redirections inside the code block . If an error occurs you'll still have to look back in your code to know which command produced the last error messages in your log-file though but at least you'll know which command failed in your code-block thanks to the success messages.
For the completeness I'll add the solution for the first approach using a function to execute your commands as I said earlier (which I actually prefer). But because exiting a script from a function can be quite complex I would rather use the idea from the second approach (conditional execution inside a code-block) to abandon execution of the rest of the commands:
#echo off
set logfile=C:\Users\path\to\logfile_errors.txt
(
call :executeOwn epmautomate login aaaa#a.com abcd123 https://www.website.com
call :executeOwn epmautomate exportdata ABC_DATA_EXTRACT
call :executeOwn epmautomate downloadfile ABC_DATA_EXTRACT.zip
call :executeOwn MOVE "C:\doc\bin\ABC_DATA_EXTRACT.zip" "C:\Users\Administrator\Documents\folder"
call :executeOwn cd "C:\Users\Administrator\Documents\folder"
call :executeOwn unzip -n "C:\Users\Administrator\Documents\folder\ABC_DATA_EXTRACT.zip"
call :executeOwn call "C:\Users\Administrator\Documents\folder\change_header.cmd"
)
REM Exit current batch script with error status from last executed call
exit /b %ERRORLEVEL%
:executeOwn
%* 2>> "%logfile%"
IF ERRORLEVEL 1 (
REM Write command that was executing to log-file
echo FAILED : [ %* ] >> "%logfile%"
REM some extra personal errormessage if needed
echo An error occured, consult the log-file for more info
REM Exit current script and set errorlevel on 1 (failure)
exit /b 1
)
echo succeeded : [ %* ] >> "%logfile%"
exit /b 0
This method will even allow to solve the problem with the variable expansion on a way that doesn't require delayed expansion. You'll just have to use %%var%% instead of the usual %var% to expand variables inside the code block and the :executeOwn will be able to expand it to its newest value. Beware: special characters like ^&<> inside the code-block will have to be escaped with a caret ^^^&^<^> in order to be executed in the :executeOwn function except if they are part of double quoted string.
I have a script a.cmd that calls another script b.cmd, and redirects its output. the called script, starts an executable that is never terminated. The output of the executable is redirected to its own log file. Simplified code:
a.cmd:
[1] #ECHO OFF
[2] SET LOG_FILE_NAME="log.txt"
[3] REM Start the b.cmd redirecting all output
[4] CALL b.cmd >> %LOG_FILE_NAME% 2>&1
[5] ECHO returned to a.cmd >> %LOG_FILE_NAME% 2>&1
[6] EXIT /B 0
b.cmd:
[1] #ECHO OFF
[2] SET ANOTHER_LOG_FILE_NAME="log2.txt"
[4] ECHO RunForEver.exe redirecting all output
[5] START CMD /C "RunForEver.exe >> %ANOTHER_LOG_FILE_NAME% 2>&1"
[6] ECHO b.cmd execution complete
[7] EXIT /B 0
(Line numbers were added for convenience)
The problem I'm encountering is that line 4 in b.cmd seems to grab a handle on the initial log file (LOG_FILE_NAME) because all b.cmd output is redirected to it, and the handle is not released while the executable (and the cmd that launched it) are running.
I didn't except this behavior because I thought only the output of the start command itself will be redirected to the LOG_FILE_NAME log file, and the output from the other process that is actually running the RunForEver.exe executable will be written to the ANOTHER_LOG_FILE_NAME.
As a result, line 5 in a.cmd errors out with access denied to LOG_FILE_NAME.
Could someone explain what's going on? Is there a way to avoid this?
I tried doing the output redirection to LOG_FILE_NAME from inside b.cmd, but then I get the access denied error in line 2 of b.cmd.
Thanks in advance!
Wow! That is a fascinating and disturbing discovery.
I don't have an explanation, but I do have a solution.
Simply avoid any additional redirection to log.txt after the never ending process has started. That can be done by redirecting a parenthesized block of code just once.
#ECHO OFF
SET LOG_FILE_NAME="log.txt"
>>%LOG_FILE_NAME% 2>&1 (
CALL b.cmd
ECHO returned to a.cmd
)
EXIT /B 0
Or by redirecting the output of a CALLed subroutine instead.
#ECHO OFF
SET LOG_FILE_NAME="log.txt"
call :redirected >>%LOG_FILE_NAME% 2>&1
EXIT /B 0
:redirected
CALL b.cmd
ECHO returned to a.cmd
exit /b
If you need to selectively redirect output in a.cmd, then redirect a non-standard stream to your file just once, and then within the block, selectively redirect output to the non-standard stream.
#ECHO OFF
SET LOG_FILE_NAME="log.txt"
3>>%LOG_FILE_NAME% (
echo normal output that is not redirected
CALL b.cmd >&3 2>&1
ECHO returned to a.cmd >&3 2>&1
)
EXIT /B 0
Again, the same technique could be done using a CALL instead of a parenthesized block.
I've developed a simple, self contained TEST.BAT script that anyone can run to demonstrate the problem. I called it TEST.BAT on my machine.
#echo off
del log*.txt 2>nul
echo begin >>LOG1.TXT 2>&1
call :test >>LOG1.TXT 2>&1
echo end >>LOG1.TXT 2>&1
exit /b
:test
echo before start
>nul 2>&1 (
echo ignored output
start "" cmd /c "echo start result >LOG2.TXT 2>&1 & pause >con"
)
echo after start
pause >con
exit /b
Both the master and the STARTed process are paused, thus allowing me to choose which process finishes first. If the STARTed process terminates before the master, then everything works as expected, as evidenced by the following output from the main console window.
C:\test>test
Press any key to continue . . .
C:\test>type log*
LOG1.TXT
begin
before start
after start
end
LOG2.TXT
start result
C:\test>
Here is an example of what happens if I allow the main process to continue before the STARTed process terminates:
C:\test>test
Press any key to continue . . .
The process cannot access the file because it is being used by another process.
C:\test>type log*
LOG1.TXT
begin
before start
after start
LOG2.TXT
start result
C:\test>
The reason I find the behavior disturbing is that I can't fathom how the STARTed process has any relationship with LOG1.TXT. By the time the START command executes, all standard output has been redirected to nul, so I don't understand how the new process knows about LOG1.TXT, let alone how it establishes an exclusive lock on it. The fact that echo ignored output has no detectable output is proof that the standard output has been successfully redirected to nul.
After invoking the CMD "echo xxx" the %errorlevel% is always 1.
Even though "echo xxx" is executed successfully.
Yes, echo something has absolutely no effect on the error level and will not change errorlevel to 0 as that will just always succeed.
For example:
running a echo something > c:\somefile.txt, which will succeed actually creating the file, but not change errorlevel to 0.
c:\>copy nil c:
The system cannot find the file specified.
c:\>echo %errorlevel%
1
c:\>echo this.works > c:\test.txt
c:\>echo %errorlevel%
1
type c:\test.txt
this.works
External commands like FINDSTR and XCOPY are actually separate programs (FINDSTR.EXE, XCOPY.EXE). External commands set the ERRORLEVEL upon both success and failure. By convention, 0 indicates success, and non-zero indicates an error. But some programs may not follow that convention.
ECHO is an internal command, meaning that the command is built into the CMD.EXE program itself. No additional program is needed. Internal commands behave differently.
If used on the command line, or within a batch script with a .BAT extension, then most internal commands set ERRORLEVEL upon failure, but do nothing to the ERRORLEVEL upon success. However, there are some exceptions. Both VER and VOL do set the ERRORLEVEL to 0 upon success.
When used within a batch script with a .CMD extension, all internal commands set the ERRORLEVEL upon both success and failure, just like external commands.
The ERRORLEVEL of 1 that you see after ECHO must have come from a prior command that failed. I've never seen ECHO fail. The only way I can imagine it could fail is if stdout is successfully redirected to a file, but the storage device cannot be written to for some reason such as if the device is full.
echo is a curious command. Let's see how it behaves
When echo command works
If errorlevel is 0 before echo, after echo, errorlevel will be 0 (the obvious case)
If errorlevel is 1 before echo, after echo, errorlevel will be 1. Echo does not change errorlevel
When echo command "fails"
Can echo fail? Let's create a case where it "fails". Open two command windows on the same directory. In first one run pause > file.txt to generate a file and place a lock on it while the pause command is waiting a keypress. In second command window run echo something > file.txt. In this case, the echo command will fail, as the first command window hold a lock on the file, so the second one is not able to write to the file. Properly talking the echo has not failed, but the redirection does, but just to see what happens
If errorlevel is 1 before running echo, it is still 1 after the echo (the obvious case)
If errolevel is 0 before running echo, it is still 0 after the echo
So, it seems that the echo command behaves identically in the two cases
BUT if we change the way echo is executed to
echo something && echo works || echo fails
then the behaviour changes a bit
When echo command works
No difference. errorlevel will not change, keeping the value it had before running the echo command.
When echo command "fails"
Using the echo something > file && echo works || echo fails then, if errorlevel is 1 before running echo, it keeps its value.
But if errorlevel is 0 and the echo command fails, in this case, with this construct of the command, errorlevel will show the failure and change its value to 1
For example, in a batch file, I typed the command below:
xcopy C:\fileA.txt D:\Dir\ /y /d
It will fail absolutely if there was not a file called fileA.txt. But I want to know if it fails, then output some messages to user. How can I do this?
Any help will be grateful, thanks!
Most commands/programs return a 0 on success and some other value, called errorlevel, to signal an error.
You can check for this in you batch for example by
if not errorlevel 0 goto ERR
xcopy errorlevels:
0 - All files were copied without errors
1 - No files were found to copy (invalid source)
2 - XCOPY was terminated by Ctrl-C before copying was complete
4 - An initialization error occurred.
5 - A disk-write error occurred.
[1] http://m.computing.net/answers/dos/xcopy-errorlevels/7510.html
I think what you are interested in is "error levels". See http://www.robvanderwoude.com/errorlevel.php. Basically, in your batch file, you can check the status code of the command (similar to Unix or Linux) by saying
IF ERRORLEVEL 1 <do something>
IF ERRORLEVEL 2 <do something>
where 1 and 2 are possible values of the status code returned by the last program executed. You can also do something like
echo %ERRORLEVEL%
to print out the status code, but note that it does not always behave like a "normal" environment variable. One thing that makes it different is that it does not show up with the "set" command.
I found the accepted answer didn't work but this did:
if %ERRORLEVEL% neq 0 goto ERR
Just use the following structure:
command > nul 2> nul && (
echo Success
REM can be multi line
) || (
echo Error
REM can be multi line
)
i try to make a loop in a .cmd file.
If test.txt is not exists then i will kill the cmd process.
#echo off
if not exists test.txt goto exit
But this code doesn't work and i don't know how to make a loop every 2 seconds.
Thanks for help.
The command is called exist, not exists:
if not exist test.txt goto :exit
echo file exists
:exit
About your loop:
I am not 100% sure, but I think there is no sleep or wait command in Windows. You can google for sleep to find some freeware. Another possibility is to use a ping:
ping localhost -n 3 >NUL
EDIT:
The Windows Server 2003 Resource Kit Tools contains a sleep.
See here for more information, too
If you need to wait some seconds use standard CHOICE command. This sample code check if file exist each two seconds. The loop ends if file exists:
#ECHO OFF
:CHECKANDWAITLABEL
IF EXIST myfile.txt GOTO ENDLABEL
choice /C YN /N /T 2 /D Y /M "waiting two seconds..."
GOTO CHECKANDWAITLABEL
:ENDLABEL
exit is a key word in DOS/Command Prompt - that's why goto exit
doesn't work.
Using if not exist "file name" exit dumps you out of that batch file.
That's fine if exiting the batch file is what you want.
If you want to execute some other instructions before you exit, change the label to something like :notfound then you can goto notfound
and execute some other instructions before you exit.
(this is just a clarification to one of the examples)
Using the following:
if not exist "file name" goto exit
Results in:
The system cannot find the batch label specified - exit
However using the same command without "goto" works, as follows:
if not exist "file name" exit