Reputation: 21
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 [email protected] 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?
Upvotes: 0
Views: 9725
Reputation: 2231
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 [email protected] 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 [email protected] 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.
Upvotes: 1