if (ms) blog++;

Random bits of (hopefully) useful technical information on Windows, with a focus on understanding and troubleshooting.

Batch Files – Basic Error Checking and Running as Scheduled Tasks

Batch Files – Basic Error Checking and Running as Scheduled Tasks

  • Comments 16
  • Likes

Batch files may be considered “old hat” by a lot of people (or a complete mystery to the younger ones!) but they are sometimes still the easiest way to execute simple jobs as they have practically no requirements other than a command shell process to run within – yes there is a strong probability that a script could achieve an identical (or at least equivalent) result, but the fact is that it will be more complex and therefore longer to create and administer.

Something that trips up a fair number of people is the difference between double-clicking a .BAT file and scheduling it as a task to run at a certain time, on a trigger or on a schedule – it can seem that the batch file just simply does not run at all, and the return code sent back to the Task Scheduler is often 0x2, “path not found”, even though the file most definitely is there.

 

When you double-click a .BAT or .CMD file through Explorer, a cmd.exe process is started to provide the environment in which it will run.
The credentials used are your own (or more correctly, inherited from the Explorer process).
The “current working directory” is set to that where the batch file lives.

When the Task Scheduler service starts the same batch file it actually invokes an instance of cmd.exe (as SYSTEM impersonating the user whose credentials are defined in the task) to carry out the process and waits for it to exit with a return code – the current working directory for this process is not that of the batch file, but that of cmd.exe itself.
i.e. %windir%\system32

 

Now, if your batch file assumes that you launched it through Explorer or from an existing command prompt with the current working directory set to its own location, you can run into problems.

One simple way to verify if your batch file should work as a scheduled task is to call it from an existing cmd.exe process from a different current working directory (such as your profile, or a temp folder).

Be very, very careful if your batch file does any kind of file deletion – especially if wildcards are involved – you do not want to be in the wrong context when doing this (especially not a system folder or your profile!).

 

The trick is to do some basic error checking at the start and throughout your batch files, and possibly even some rudimentary logging by piping messages to a file.

One simple solution is to have the batch file set the current working directory itself as one of its first tasks.
e.g. cd /d “J:\Batch Jobs\mrsnrub”
(The “/d” switch is used to set the current drive as well as the directory, and the quotation marks are essential if the path contains any spaces.)

An alternative is to have every action explicitly have a fully qualified path, but this can make things a lot harder to read later.

 

As far as basic error checking goes, it is always wise to check for the existence of essential files before working with them – a simple “if exist” condition can control a single statement (or alternatively “if not exist” can be used to jump to somewhere else in the batch file).

For simple logging, the “>” instruction will start a new log and “>>” will append to it.

 

Putting the theory together for a simple example:

@echo off
echo [%date% - %time%] Log start > %temp%\MyBatch.log
if not exist "J:\Batch Jobs\mrsnrub\" goto ERROR1
cd /d "J:\Batch Jobs\mrsnrub"
if exist "GetFiles.txt" ftp –s:GetFiles.txt
goto END
:ERROR1
echo Unable to locate folder containing FTP script, aborting
echo ERROR – folder not present >> %temp%\MyBatch.log
:END

This isn’t the most flexible of solutions and it doesn’t log success or failure of attempting to launch FTP.EXE with the script file, but hopefully it should serve as an example of how to achieve basic logging and error checking (including piping the current time and date to a log file, as this is often essential).

 

One last useful tip is to output or log the “last error” as the last action, using the ERRORLEVEL variable within the batch file – 0 is generally used to indicate “no error”:
ECHO Batch file return code: %ERRORLEVEL%
ECHO Batch file return code: %ERRORLEVEL% >> %temp%\MyBatch.log

 

One of the best friends you have for debugging batch files is Process Monitor – this lets you verify your batch file (as a cmd.exe process, remember) and lets you see what it was trying to do, along with the results, in particular the Tools / Process Tree view is of great value when looking at the parent/child relationship between processes (ensure to uncheck the “Only show processes still running at the end of current trace” option).

There is a lot more power to batch file programming, this is just scratching the surface, but the main principle here is to ensure you have the right context when executing commands.

Comments
  • Can you tell me how to specify command if connect to ftp fail or files could not be copied? thank you

  • Unfortunately, as ftp.exe just returns the success level of finding the script file to execute, it will return 0 (ERROR_SUCCESS) regardless of whether the operations themselves worked or not.

    I suggest that you use "if not exist" method after ftp.exe has processed the script to see if the files you attempted to download are present... of course that doesn't help if you are uploading rather than downloading... in which case I would look for a more advanced FTP transfer program that handles batch/shceduled jobs and retries.

  • Hi,

     I have a batch file which is calling a exe file..When exe file execution completes I want to check whether the execution is successful or not from my batch file. Can it be done?

     Exe file is also writing a log file, Can I read that log file through batch file?

    Thanks

  • Hi Baljeet,

    You can use %ERRORLEVEL% as described to check the status after calling the executable - if it is 1 (or higher) then some kind of error was returned either in calling the executable (e.g. not found, access denied) or by the executable itself.

    Batch files are limited in their ability to handle files, and logs are generally manually reviewed to see if corrective action is needed - return values & error levels are typically used for automated error handling.

    Automated parsing of log files usually requires a lot more complex actions, such as regular expressions or a grep-like utility.

    Cheers,

    Paul

  • Hi,

    How do I write a batch file to copy files when the starting file location is uncertain (eg running from a usb stick on a random computer) as a batch file starts running from %windir%\system32?

  • Hi Jon,

    I'm not sure how you can schedule a task without knowing the path to the batch file... but in case you're looking at simply picking up the drive letter for a batch file launched interactively by a user, the environment variable %CD% can be used to get the folder for a batch file when invoked through Explorer, and if you wanted only the drive letter then you can use %CD:~0,2%.

    Otherwise, I can only suggest cycling through the drive letters using "IF EXIST" to find the batch file name, but that might throw "drive not ready" errors for removable drives such as CD/DVD drives & card readers.

    Cheers,

    Paul

  • Hi Paul, would you have any hint that, I have a scheduled task (bat) to compress a folder with 7z and then send the file out for storage.  The path and things are all set.  Well, the scheduled task was set for allow on-demand trigger, and I have tested it by right click on the task and then "RUN".  Everything goes correctly.  However, at the set time (4am daily or so), it always does things differently with an error.  Specifically, in the bat I have this line

    7za a back_%date:~10,4%-%date:~7,2%-%date:~4,2%_%time:~0,2%.%time:~3,2%.%time:~6,2%.zip back\

    Which backup the <back> sub directory into a <current_time>.zip file.

    When the task was triggered on-demand, the backup was good and the tasks afterwards were good.

     sample file name: back_2012-10-22_15.11.57.zip

    When the task was triggered by the set time, the backup file was somehow weird

     sample file name: back_2012-10-24_.7z

    Then since the compressed file name was not in the correct format, the tasks to follow were not executed correctly.

    Do you have any idea on the details of the syntax I have used and the difference between on-demand trigger and auto-trigger?

    Thanks.

  • Hi Kevin,

    My initial thought is the system locale.

    The date string looks to be good, but clearly the string is getting terminated too early - you specify you want a .zip file, but end up with teh default .7z extension in the case.

    This would imply that "%time:~0,2%" is coming out as null or it may contain, say, a comma?

    The formatting of date & time strings is based on the locale of the user profile under which the process is launched.

    The way I would typically troubleshoot this would be to make the task interactive, echo the strings to the console and put a pause at the end, then schedule it for 1 minute in the future.

    If you are using Windows Vista or later, the tasks running in session 0 don't have a desktop to display to, so when logged on as an administrative user you should now get a prompt that there is something trying to display a message or prompt on the non-interactive desktop session (0) and you can step through to see & acknowledge it.

    Alternatively, you could pipe the output as a string into a file using ECHO.

    e.g. echo Resulting filename: "back_%date:~10,4%-%date:~7,2%-%date:~4,2%_%time:~0,2%.%time:~3,2%.%time:~6,2%.zip" > foo.txt

    HTH,

    Paul

  • The problem was actually that if I schedule it at 4am, the 2 digit for "hour" would actually have a space like " 4" rather than "04" and the result would be funny.

    Thanks for the help.

  • Ah, yes, I see - the "%time:0,2%" will indeed pull exactly 2 characters from the %time% string, including a leading space rather than a 0 if that is how the locale is set up.

    What you could do is to store "%time:0,1%" into a temporary variable, then test it against " " - if it matches then reset it to "%time:1,1%", then use the temporary variable in the filename (whether or not it was altered, it will now be correct).

    You also have the option to prepend a leading 0 if you wish, to help with sorting by filename or consistency of the naming convention.

    HTH,

    Paul

  • Hi Kevin,

    i use this script to compress all .txt and .cpi files into the backup folder in separated files with 7zip. After the files are zipped i delete the original files. However this script has a logical flaw. Lets say if the 7zip program fails to run, the files will also get deleted. How can i change the script so that it should not delete the files if they don't get zipped first. Also how can i change this script so it zips files that are older than 7 days? Thanks for your help.

    @echo off

    setlocal

    set _source=C:\test7zip\bak

    set _dest=C:\test7zip\bak

    set _wrpath=C:\Program Files\7-Zip

    if NOT EXIST %_dest% md %_dest%

    for %%I in (%_source%\*.txt,%_source%\*.cpi) do "%_wrpath%\7z" a "%_dest%\%%~nI.7z" "%%I" & del "%%I"

    pause

    Thanks,

    Jason

  • Perfect! My issue fixed. Thank you!

  • good one.

  • I am also making a batch that could open the file but it opens every file written to it plz help
    echo off
    Title GAME @ DX's KKS
    set /p ks=Select any 1 game and Type its name {WWE, NFS, COD, MAX PAYNE, ENTER THE MATRIX or Resident evil (Type as given here)}
    if %ks%==WWE goto raw
    if %ks%==wwe goto raw
    if %ks%==NFS goto speed
    if %ks%==nfs goto speed
    if %ks%==COD goto duty
    if %ks%==cod goto duty
    if %ks%==max payne goto kk
    if %ks%==MAX PAYNE goto kk
    if %ks%==enter the matrix goto 123
    if %ks%==ENTER THE MATRIX goto 123
    if %ks%==resident evil goto evil
    if %ks%==RESIDENT EVIL goto evil
    :raw
    "D:\WORLD CLASS APPS\KKS\WWE\RAW.exe"
    cls
    :speed
    "D:\WORLD CLASS APPS\NEED FOR SPEED GAMES\NFS U2\speed2.exe"
    cls
    :duty
    "D:\WORLD CLASS APPS\Call of Duty\Data\CoDSP.exe"
    cls
    :kk
    "D:\WORLD CLASS APPS\Max payne 2\Max Payne 2\MaxPayne2.exe"
    cls
    :123
    "D:\WORLD CLASS APPS\MATRIX\Matrix.exe"
    cls
    :evil
    "D:\WORLD CLASS APPS\Resident Evil 4\game_me.exe"
    cls
    PAUSE

  • You need a bunch of "GOTO :EOF" statements there - first before the ":raw" label just so if you edit the set of games later but forget to add a new case, and as a replacement for every instance of "cls".

    You don't need the pause either - if the idea is to keep the command prompt window open while the selected game is playing then use "start /w" to launch the executable and wait until it exits.

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment