The batch has to remove files and directories from specific locations and output success or stdout/stderr messages to a new .txt file. I have created the most of the script and it performs exactly as it should, except when the deletion is successful it moves forward to the next line rather than echo a 'successful' message on the log.
echo Basic Deletion Batch Script > results.txt
#echo off
call :filelog >> results.txt 2>&1
notepad results.txt
exit /b
:filelog
call :delete new.txt
call :delete newer.txt
call :delete newest.txt
call :remove c:\NoSuchDirectory
GOTO :EOF
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
if errorlevel 0 echo succesful
GOTO :EOF
:remove
echo deleting directory %1
rmdir /q /s %1
GOTO :EOF
For some reason I can't find the syntax for if del succeeds echo 'successful'. In the above example if I remove the line
if errorlevel 0 echo successful
Everything works fine, but no success message. With this line left in it echoes success for every line.
del and ErrorLevel?
The del command does not set the ErrorLevel as long as the given arguments are valid, it even resets the ErrorLevel to 0 in such cases (at least for Windows 7).
del modifies the ErrorLevel only in case an invalid switch is provided (del /X sets ErrorLevel to 1), no arguments are specified at all (del sets ErrorLevel to 1 too), or an incorrect file path is given (del : sets ErrorLevel to 123), at least for Windows 7.
Possible Work-Around
A possible work-around is to capture the STDERR output of del, because in case of deletion errors, the related messages (Could Not Find [...], Access is denied., The process cannot access the file because it is being used by another process.) are written there. Such might look like:
for /F "tokens=*" %%# in ('del /F /Q "\path\to\the\file_s.txt" 2^>^&1 1^> nul') do (2> nul set =)
To use the code in command prompt directly rather than in a batch file, write %# instead of %%#.
If you do not want to delete read-only files, remove /F from the del command line;
if you do want prompts (in case wildcards ? and/or * are present in the file path), remove /Q.
Explanation of Code
This executes the command line del /F /Q "\path\to\the\file_s.txt". By the part 2>&1 1> nul, the command output at STDOUT will be dismissed, and its STDERR output will be redirected so that for /F receives it.
If the deletion was successful, del does not generate a STDERR output, hence the for /F loop does not iterate, because there is nothing to parse. Notice that ErrorLevel will not be reset in that case, its value remains unchanged.
If for /F recieves any STDERR output from the del command line, the command in the loop body is executed, which is set =; this is an invalid syntax, therefore set sets the ErrorLevel to 1. The 2> nul portion avoids the message The syntax of the command is incorrect. to be displayed.
To set the ErrorLevel explicitly you could also use cmd /C exit /B 1. Perhaps this line is more legible. For sure it is more flexible because you can state any (signed 32-bit) number, including 0 to clear it (omitting the number clears it as well). It might be a bit worse in terms of performance though.
Application Example
The following batch file demonstrates how the above described work-around could be applied:
:DELETE
echo Deleting "%~1"...
rem this line resets ErrorLevel initially:
cmd /C exit /B
rem this line constitutes the work-around:
for /F "tokens=*" %%# in ('del /F /Q "C:\Users\newuser\Desktop\%~1" 2^>^&1 1^> nul') do (2> nul set =)
rem this is the corrected ErrorLevel query:
if not ErrorLevel 1 echo Deleted "%~1" succesfully.
goto :EOF
Presetting ErrorLevel
Besides the above mentioned command cmd /C exit /B, you can also use > nul ver to reset the ErrorLevel. This can be combined with the for /F loop work-around like this:
> nul ver & for /F "tokens=*" %%# in ('del /F /Q "\path\to\the\file_s.txt" 2^>^&1 1^> nul') do (2> nul set =)
Alternative Method Without for /F
Instead of using for /F to capture the STDERR output of del, the find command could also be used like find /V "", which returns an ErrorLevel of 1 if an empty string comes in and 0 otherwise:
del "\path\to\the\file_s.ext" 2>&1 1> nul | find /V "" 1> nul 2>&1
However, this would return an ErrorLevel of 1 in case the deletion has been successful and 0 if not. To reverse that behaviour, an if/else clause could be appended like this:
del "\path\to\the\file_s.ext" 2>&1 1> nul | find /V "" 1> nul 2>&1 & if ErrorLevel 1 (1> nul ver) else (2> nul set =)
Different Approach: Checking File for Existence After del
A completely different approach is to check the file for existence after having tried to delete it (thanks to user Sasha for the hint!), like this, for example:
del /F /Q "\path\to\the\file_s.txt" 1> nul 2>&1
if exist "\path\to\the\file_s.txt" (2> nul set =) else (1> nul ver)
When using this syntax, instead of this
if errorlevel 0 echo successful
you can use this - because errorlevel 0 is always true.
if not errorlevel 1 echo successful
Just use rm from UnxUtils (or gow or cygwin). It sets the errorlevel correctly in case of a nonexistent file, or any errors deleting the file.
This was added as an edit by the original asker, I have converted it to a community wiki answer because it should be an answer, not an edit.
I found out how to do it... one way anyway.
echo Startup > results.txt
#echo off
call :filelog >> results.txt 2>&1
notepad results.txt
exit /b
:filelog
call :delete new.txt
call :delete newer.txt
call :delete newest.txt
call :remove c:\NoSuchDirectory
GOTO :EOF
:delete
echo deleting %1
dir c:\users\newuser\Desktop\%1 >NUL 2>&1
SET existed=%ERRORLEVEL%
del /f /q c:\Users\newuser\Desktop\%1
dir c:\users\newuser\Desktop\%1 2>NUL >NUL
if %existed% == 0 (if %ERRORLEVEL% == 1 echo "successful" )
GOTO :EOF
:remove
echo deleting directory %1
rmdir /q /s %1
GOTO :EOF
IF ERRORLEVEL 0 [cmd] will execute every time because IF ERRORLEVEL # checks to see if the value of ERRORLEVEL is greater than or equal to #. Therefore, every error code will cause execution of [cmd].
A great reference for this is: http://www.robvanderwoude.com/errorlevel.php
>IF /?
Performs conditional processing in batch programs.
IF [NOT] ERRORLEVEL number command
IF [NOT] string1==string2 command
IF [NOT] EXIST filename command
NOT Specifies that Windows should carry out
the command only if the condition is false.
ERRORLEVEL number Specifies a true condition if the last program run
returned an exit code equal to or greater than the number
specified.
I would recommend modifying your code to something like the following:
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
if errorlevel 1 (
rem This block executes if ERRORLEVEL is a non-zero
echo failed
) else (
echo succesful
)
GOTO :EOF
If you need something that processes more than one ERRORLEVEL, you could do something like this:
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
if errorlevel 3 echo Cannot find path& GOTO :delete_errorcheck_done
if errorlevel 2 echo Cannot find file& GOTO :delete_errorcheck_done
if errorlevel 1 echo Unknown error& GOTO :delete_errorcheck_done
echo succesful
:delete_errorcheck_done
GOTO :EOF
OR
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
goto :delete_error%ERRORLEVEL% || goto :delete_errorOTHER
:delete_errorOTHER
echo Unknown error: %ERRORLEVEL%
GOTO :delete_errorcheck_done
:delete_error3
echo Cannot find path
GOTO :delete_errorcheck_done
:delete_error2
echo Cannot find file
GOTO :delete_errorcheck_done
:delete_error0
echo succesful
:delete_errorcheck_done
GOTO :EOF
The answer of aschipfl is great (thanks, helped me a lot!) using the code under Presetting ErrorLevel you get a nice standard function:
Take care to use %~1 instead of %1 in the del statement, or you will get errors if you use a quoted filename.
::######################################################################
::call :DELETE "file.txt"
::call :DELETE "file.txt" "error message"
:DELETE
>nul ver && for /F "tokens=*" %%# in ('del /F /Q "%~1" 2^>^&1 1^> nul') do (2>nul set =) || (
if NOT .%2==. echo %~2
)
goto :EOF
BTW 1: You can give a nifty error message as a second parameter
BTW 2: Using :: instead of REM for comments makes the code even more readable.
Code:
Error Code: (What you did)
if errorlevel 0 echo succesful
The problem here is that you aren't calling errorlevel as a variable and plus you didn't add in the operator to the statement as well.
Correct Code: (Here is what it should actually be.)
if %ERRORLEVEL% EQU 0 echo succesful
Definitions:
EQU: The EQU stands for Equal. This kind of operator is also called a relational operator. Here is the documentation link to operators if you wanna know more, there are other ones but this helped me.
ERRORLEVEL: is declared as a variable and usually get the error level of the last command run usually. Variables are usually called when they are between percent signs like this
%foo%
For some more help on variables, go to cmd (Which you can go to by searching it on windows 10) and type in "set /?", without the quotes. the set command is the command you use to set variables
Related
I have a batch file with 10 lines and 5 functions in a batch script. How can I ensure that all the commands in a batch file are successful.
In other way, what's the logic to calculate return code of each command at the end of a script.
1. #ECHO OFF
2. if not exist "%Destination%\%NAME%" md %Destination%\%NAME%
3. if not exist "%Destination%\%NAME2%" md %Destination%\%NAME2%
4. rmdir %Destination%\%NAME3%
5. if not exist "%Destination%\NAME4%" md %Destination%\%NAME4%
6. cd /d X:\test1
in the above 5 lines, 4th line returns %ERRORLEVEL% 1 and 6th line returns the same. But, I could not put IF %ERRORLEVEL%==0 after every command. So, how could i script to handle this.
You should firstly save your file as .cmd instead of .bat for better error handling. Also always enclose your paths with double quotes. Then I suggest you test existance as well to overcome errorlevel.
If exist "%Destination%\%NAME3%" rmdir "%Destination%\%NAME3%"
For the code example I suggest following:
#echo off
rem Verify the existence of all used environment variables.
for %%I in (Destination NAME NAME2 NAME3 NAME4) do (
if not defined %%I (
echo Error detected by %~f0:
echo/
echo Environment variable name %%I is not defined.
echo/
exit /B 4
)
)
rem Verify the existence of all used directories by creating them
rem independent on existing already or not and next verifying if
rem the directory really exists finally.
for %%I in ("%Destination%\%NAME%" "%Destination%\%NAME2%") do (
md %%I 2>nul
if not exist "%%~I\" (
echo Error detected by %~f0:
echo/
echo Directory %%I
echo does not exist and could not be created.
echo/
exit /B 3
)
)
rem Remove directories independent on their existence and verify
rem if the directories really do not exist anymore finally.
for %%I in ("%Destination%\%NAME3%") do (
rd /Q /S %%I 2>nul
if exist "%%~I\" (
echo Error detected by %~f0:
echo/
echo Directory %%I
echo still exists and could not be removed.
echo/
exit /B 2
)
)
cd /D X:\test1 2>nul
if /I not "%CD%" == "X:\test1" (
echo Error detected by %~f0:
echo/
echo Failed to set "X:\test1" as current directory.
echo/
exit /B 1
)
This batch file handles nearly all possible errors which could occur during execution of this batch file. A remaining problem could be caused by an environment variable containing one or more double quotes in its value. The solution would be using delayed expansion.
Linux shell script interpreters have the option -e to exit immediately execution of a script if any command or application returns with a value not equal 0. But Windows command interpreter cmd.exe does not have such an option. The options of cmd.exe can be read on running in a command prompt window cmd /?.
So it is necessary to use in a batch file:
if exist "..." exit /B 1 or goto :EOF
if not exist "..." exit /B 1 or goto :EOF
if errorlevel 1 exit /B 1 or goto :EOF
... || exit /B 1 or ... || goto :EOF
See also the Stack Overflow articles:
Windows batch files: .bat vs .cmd?
Where does GOTO :EOF return to?
Single line with multiple commands using Windows batch file
What are the ERRORLEVEL values set by internal cmd.exe commands?
Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?
I've tried to write a batchfile, which would check it's own directory for other batch files and check these for a certain text. If the text is found, I want the program to jump to the end, otherwise copy itself into the found file. Here's how I tried:
rem windowsisajoke
for %%f in (*.bat) do (set A=%%f)
set FILE=%A%
set CONTENT=windowsisajoke
findstr /i "%CONTENT%" %FILE% >NUL
if errorlevel 0 goto end
copy %0 %A%
:end
if errorlevel 0 actually means "if errorlevel is zero or greater". (I know - very intuitive...)
Either change your logic:
if errorlevel 1 copy %0 %A%
or use
if %errorlevel%==0 goto :end
Given two directories c:\foo and c:\bar I want to delete the files in c:\bar that are identical to files present in c:\foo. I can use the fc command to compare each file in c:\bar with a file of the same name in c:\foo and delete duplicates manually. Is there a simple way to automate this using CMD?
If identical means similar or alike in every way: in every way, not only in date and size, therefore forced binary comparison:
#ECHO OFF >NUL
SETLOCAL enableextensions
pushd "D:\bat\FooBar"
for /F "delims=" %%G in ('dir /B /A:-D *.*') do (
call :proFC "%%~fG" "D:\bat\FooFoo\%%~nxG"
)
popd
ENDLOCAL
goto :eof
:raiseerror
exit /B %1
:proFC
call :raiseerror 321
fc /B "%~1" "%~2" >NUL 2>&1
if %errorlevel% EQU 0 (
echo del "%~1"
) else (
echo %errorlevel% "%~2"
)
goto :eof
Commented crucial points in the above script:
pushd ... switches current working directory
for /F ... loop treats static file list of the Bar folder
call :proFC ... with properly quoted line arguments parameters
popd switches current working directory back
goto :eof ends the script
:raiseerror subroutine returns exit code via exit /B %1
:proFC productive subroutine
call :raiseerror 321 important as For an invalid switch (with two passed files) an error message is printed but the errorlevel is not changed
fc /B "%~1" "%~2" >NUL 2>&1 output and error messages redirected to NUL as the errorlevel is important;
if %errorlevel% EQU 0 (
echo del "%~1" a file delete merely _echo_ed for debugging purposes
) else (
echo %errorlevel% "%~2" for debugging purposes (see below).
goto :eof returns from the subroutine
FC will set an ErrorLevel as follows (but see a note at the call :raiseerror 321 point):
-1 Invalid syntax (e.g. only one file passed)
0 The files are identical.
1 The files are different.
2 Cannot find at least one of the files.
#echo off
cd c:\bar
for %%a in (*.*) do for %%b in ("c:\foo\%%a") do (
if exist "%%b" (
if "%%~Ta %%~Za" equ "%%~Tb %%~Zb" (
del "%%a"
) else (
fc "%%a" "%%b" > NUL
if not errorlevel 1 del "%%a"
)
)
)
If two files may have different modification dates but be equal, remove the %%~T.. parts in the comparison.
I am trying to cache clear in Mozilla browser by following command
#echo off
set DataDir=C:\Users\%USERNAME%\AppData\Local\Mozilla\Firefox\Profiles
del /q /s /f "%DataDir%"
rd /s /q "%DataDir%"
for /d %%x in (C:\Users\%USERNAME%\AppData\Roaming\Mozilla\Firefox\Profiles\*) do del /q /s /f %%x\*sqlite
It is working fine but how can i make sure that execution of the command is completed i.e. cache in mozilla browser is cleared.
Ok, so as Christian.K stated, you can use %ERRORLEVEL% to determine the success or failure of each command executed. Lasse V. Karlsen stated, you can fail on this %ERRORLEVEL%'s value. Example using your code below:
#echo off
set DataDir=C:\Users\%USERNAME%\AppData\Local\Mozilla\Firefox\Profiles
del /q /s /f "%DataDir%"
if %ERRORLEVEL% NEQ 0 goto :failure
rd /s /q "%DataDir%"
if %ERRORLEVEL% NEQ 0 goto :failure
setlocal enabledelayedexpansion
for /d %%x in (C:\Users\%USERNAME%\AppData\Roaming\Mozilla\Firefox\Profiles\*) do (
del /q /s /f %%x\*sqlite
if !ERRORLEVEL! NEQ 0 goto :failure
)
endlocal
goto :EOF
:failure
echo do something here
exit
goto :EOF
Use NEQ 0 for testing %ERRORLEVEL%. I have seen this value be something other than 1 when a command fails.
setlocal enabledelayedexpansion handles the %ERRORLEVEL% variable changing after each execution of the for loop. Without this, it would just evaluate to whatever was contained in the %ERRORLEVEL% variable when the for loop was entered.
goto :EOF at the end of your main script so your labels are not executed after the script completes. Also add this at the end of your label to prevent the same (the exit I have in mine would prevent that as well, but you may not want to exit the script on failure).
To handle spaces etc in usernames you can use this as your last command:
for /d %%x in ("C:\Users\%USERNAME%\AppData\Roaming\Mozilla\Firefox\Profiles\*") do del /q /s /f "%%x\*.sqlite"
This also assumes that the files you are deleting have a .sqlite extension.
The files will probably be locked if they are in use, if the browser hasn't been closed.
My working batch file scans a long list of remote servers, copies anything there to a local server, checks the log file for a keyword, and if the keyword is found sends an email. I noticed it is always sending emails, even with a blank log file.
I discovered both FOR loops are using the %1 variable for their output - as seen in ECHO %1 and each line of the called :servermove. For lack of a better explanation it is not resetting %1 to null between loops.
I reviewed almost a dozen SO posts and am somewhat confident using SETLOCAL ENABLEDELAYEDEXPANSION would resolve this. That is where my understanding ends and I am unsuccessful thus far.
Here is the relevant code:
SET DATE=%date:~4,2%-%date:~7,2%-%date:~10,4%
SET HH=%time:~0,2%
SET MN=%time:~3,2%
SET TSTAMP=Time Run is %HH%%MN%
SET DATETIME=%DATE% at %HH%%MN%
SET LOGFILE="\\nt980a3\CreditFileImagesTransmission\LogFiles\%DATETIME%-File Move Log.txt"
SET MailDst=
SET MailSrc=
SET MailSrcName=Center to LDSD File Mover
SET OKMailSub=A Branch Has Sent You Some Files
ECHO %DATETIME% > %LOGFILE%
ECHO. >> %LOGFILE%
FOR /F "tokens=1" %%A IN (%~dp0SourceServers.txt) DO CALL :ServerMove %%A
:cleanuplogs
PUSHD "\\nt980a3\CreditFileImagesTransmission\LogFiles" &&(
FORFILES /S /M *.txt /D -45 /C "CMD /C DEL /Q #path"
) & POPD
:mailtest
FOR /F "tokens=*" %%A IN (%LOGFILE%) DO CALL :searchlog "%%A"
:searchlog
ECHO %1 | find "\\nt">NUL
IF NOT ERRORLEVEL 1 GOTO successmail
GOTO exit
:successmail
IF EXIST %temp%\to.txt DEL %temp%\to.txt
FOR %%a IN (%MailDst%) DO ECHO %%a>>%temp%\to.txt
"%~dp0sendmail.exe" /TO=%temp%\to.txt /FROM=%MailSrcName% ^<%MailSrc%^> /REF=%OKMailSub% /MESSAGE=%LOGFILE% /HOST=
:exit
EXIT
:ServerMove
DIR /S /B \\%1\CreditFileImagesTransmission\*.* >> %LOGFILE%
XCOPY /E /C /I /Y "\\%1\CreditFileImagesTransmission\*.*" "\\nt980a3\CreditFileImagesTransmission\%DATE%\%HH%%MN%\"
FOR /D %%P IN ("\\%1\CreditFileImagesTransmission\*.*") DO RMDIR "%%P" /Q /S
DEL /Q /S "\\%1\CreditFileImagesTransmission\*.*"
I tried changing :mailtest to use %%B in both instances but that also fails. Placing SETLOCAL ENABLEDELAYEDEXPANSION and its counterpart ENDLOCAL before one or the other loop and changing the %%A to !A! does not work either.
Would someone kindly point out the error in my ways and offer suggestions or resources that will help me resolve this?
%1 is the first parameter provided to the procedure - either from the command-line (in the main procedure) or the parameter following the procedure name in call :procedurename parameter1.
In your case, %1 to :servermove is an entry from SourceServers.txt and %1 to :searchlog is each line from %LOGFILE%.
Since you've censored your batch, what you've posted makes little sense. For instance, the :searchlogs routine will take the first line from %LOGFILE% and go to successmail or cleanlogs depending on whether that first line contains the target string \\nt. What it does from there, we can't tell.
We're faced with an XY problem - trying to fix a solution, not a problem.
First problem: Don't use date as a user-variable. It's a "magic variable" which contains the date but it's overridden by a specific set statement.
Having run :servermove for each entry in SourceServers.txt, you are
- accumulating a directory list from \CreditFileImagesTransmission\*.* on that server.
- copying those files to server nt980a3 with a date/timestamp but not including the source-servername so any duplicate name anywhere will overwrite an earlier version. I suggest you include %1 into your destination-name.
- deleting subdirectories
- deleting files.
I'd suggest you simply remove the directory \\%1\CreditFileImagesTransmission\, then re-create it.
I'd also suggest that you add an extra line
goto :eof
after the del /q /s... line. This will cause execution to be transferred to the end-of-file (the colon in :eof is required) and may seem superfluous, but it ensures that the routine has a defined endpoint - if you add a further routine, there is no way the :servermove routine will continue into your new code.
After each server has been processed, you proceed to the :cleanuplogs routine, which I presume deletes logs older than 45 days.
Your next statement is a real problem. What it will do is grab the very first line of the logfile (which contains "%DATE% at %HH%%MN%" with the date resolved as you've set at the start and it then processes this line in :searchlog; there is no \\nt in this line, so errorlevel is set to 1, and the batch proceeds to :EXIT (not a good label in my view, since it's a keyword); executes an exitand should terminate the batch.
This appears not to be what it is actually doing, and I'm at a loss to explain why.
I'd suggest changing
:mailtest
FOR /F "tokens=*" %%A IN (%LOGFILE%) DO CALL :searchlog "%%A"
:searchlog
ECHO %1 | find "\\nt">NUL
IF NOT ERRORLEVEL 1 GOTO successmail
GOTO exit
to
:mailtest
find "\\nt" %LOGFILE%>NUL
IF NOT ERRORLEVEL 1 GOTO successmail
:failmail
echo "\\nt" was found in the log
pause
GOTO exit
but I can't test that...
:mailtest
FOR /F "tokens=*" %%A IN (%LOGFILE%) DO CALL :searchlog "%%A"
You are missing a GOTO :EOF or similar goto here because it will drop through to the routine below once the above is finished.
:searchlog
ECHO %1 | find "\\nt">NUL
IF NOT ERRORLEVEL 1 GOTO successmail
GOTO exit
I feel you can not carry the %1 of first for loop to others. Try to transfer that to another variable like below.
:ServerMove
set servername=%1
DIR /S /B \\%servername%\CreditFileImagesTransmission\*.* >> %LOGFILE%
XCOPY /E /C /I /Y "\\%servername%\CreditFileImagesTransmission\*.*" "\\nt980a3\CreditFileImagesTransmission\%DATE%\%HH%%MN%\"
FOR /D %%P IN ("\\%servername%\CreditFileImagesTransmission\*.*") DO RMDIR "%%P" /Q /S
DEL /Q /S "\\%servername%\CreditFileImagesTransmission\*.*"
Cheers, G