Workaround for failing DEL not setting errorlevel [duplicate] - cmd

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

Check equivalence of variable in FOR /f loop batch

I do my best to be clearer this time!
I am writing a .bat file to compile single (for the moment) files of different supported languages (fortran, C, C++, etc..). Since for the moment it is for a single file, I made up with this architecture:
buildfile [-lang] filename
where if specified -lang can be either -cpp, -c, -for, etc.. If not specified, -lang will be assumed from file extension.
Now, I report the first piece of code (very beginning, so nothing comes before):
#echo off
:: check first input
if "%1"=="" goto :syntax
if "%1"=="-h" goto :syntax
if "%1"=="/h" goto :syntax
if "%1"=="/?" goto :syntax
if "%1"=="--help" goto :syntax
if "%1"=="/help" goto :syntax
echo %1 | findstr "^-" > inp.log
echo Not found >> inp.log
set "var="
for /f "tokens=* delims=" %%i in (inp.log) do (
echo Big I writes %%i
set "var=%%i"
set var
if "%var%"=="Not found" (
echo String not found
goto :end
if "%~x1"=="" goto :syntax
)
goto :end
)
After check if user asked for help, I want to check if character "-" is present (that means if -lang has been specified).
As first I had thought to redirect echo %1 | findstr "^-" > %avariable% and then if "%avariable%"=="" then character "-" was not specified, hence go to check for file extension with "%~x1" (DID NOT WORK).
Second I thought to place the findstr command in echoing %1 directly as the argument of the FOR /F loop, but if "-" was not present that exploded since the searching string was empty! (i.e. for /f "tokens=* delims=" %%i in (' echo %1 ^| findstr "^-" ') do ( )
So, lastly is what you see in the piece of code, writing output into a file and rereading it, but there's something not working properly.
I added the line "Not found" to avoid reading an empty file (since apparently was giving same error as option 2).
I see that when I do set var I see correctly "var=Not found", that would mean that var is correctly set.
But as soon as I get to the IF condition inside the FOR /F loop, that does not work.
I can imagine a much better and cleaner solution exists, so I am here to ask your help.
I would say same something not far from option 1 could be best, since you only do 2 operation (redirect and then IF condition), maybe I am missing some syntax to make it working.
Many thanks!
EDIT:
of course, if "-" character is found, then I do a simple spell check to assume language (via many IF statements)
PS: all goto are there as debug.
EDIT EDIT:
it seems I solved the problem with removing var variable, using directly %%i one:
#echo off
:: check first input
if "%1"=="" goto :syntax
if "%1"=="-h" goto :syntax
if "%1"=="/h" goto :syntax
if "%1"=="/?" goto :syntax
if "%1"=="--help" goto :syntax
if "%1"=="/help" goto :syntax
echo %1 | findstr "^-" > inp.log
echo Not found>> inp.log
set "var="
for /f "tokens=* delims=" %%i in (inp.log) do (
echo Big I writes %%i
if "%%i"=="Not found" (
echo String not found
echo %~x1
if "%~x1"=="" goto :syntax
) else (
echo String found!
if "%1"=="-cpp" shift & goto :cppfile
if "%1"=="-c" shift & goto :cppfile
if "%1"=="-for" shift & goto :fortranfile
:: if one comes here, format not supported.
goto :syntax
)
)

Batch file for finding top N files which are not of a perticular type

Hi I want some basic help with Windows commands to automate some of my work.
I have a folder in which I get some files, I need to run a fix process in order to correct the file contents.
#echo off
setlocal
set /a "n=0, limit=3"
>"testfile1.txt" (
for /f %%F in ('dir /o-d /b *_SourceFile_*.csv') do (
set %x= echo %%F |findstr /i/v "\.fixed.csv"
if %x% not nul
(
FixFileWithWinFormat.exe %%F
2>nul set /a "n+=1, 1/(limit-n)"||goto :break
)
)
)
:break
echo 'competed'
This bit of code above if I comment out the %x is working the if condition is not making it to work. Don't know why. It could be silly.
set %x= echo %%F |findstr /i/v "\.fixed.csv"
if %x% not nul
These two lines are incorrect. It's better to state what the code is intended to do, otherwise we're guessing.
The set statement can't be used to set an environment variable in that manner - it's very simple, set var=string and %x is an invalid variable to set.
The not nul idea can be accomplished by if not defined x - but no %s.
So - assuming you wish to execute the following parenthesised statement-sequence if the filename in %%f is not found in the file, then
findstr /i "%%f" ".\fixed.csv" >nul
if errorlevel 1 (your statementsequence in parentheses)
should do the task. I'm not sure of the filename fixed.csv here. \.fixed.csv will locate a filename .fixed.csv in the root directory, whereas .\fixed.csv will locate a file fixed.csv in the current directory (and hence the .\ is redundant.)
findstr will find the string contained in %%f in the file, with /i making the seach case-insensitive. >nul redirects any output to nowhere. errorlevel is set to 0 if the text is found, non-zero otherwise.
if errorlevel 1 means "if errorlevel is 1 or greater". Note that if errorlevel 0 means "if errorlevel is 0 or greater" (ie. always, for all intents and purposes) hence to detect errorlevel 0 (ie "text found" you need if not errorlevel 1)
and the opening parenthesis must be on the same physical line as the if
(not sure about your terminating condition; seems to be attempting to force a divide-by-zero after limit iterations. Don't have the time to test atm - soz)

Findstr batch file in finding listener down servers

Hi I'm trying to create a batch file to filter out servers which has RDP/ICA listener down from a list of servers in a notepad file, I created this script with the below syntax, but for some reasons it won't work as expected, can some one help me fix the situation?
I've a list of servers in computer.txt file and I'm trying to find the one's which are down and if errorlevel is 0, meaning the string down is found, I want the server name to be printed in listenerdown.txt , but for some reasons, if I execute the batch file, all the servers in computer.txt gets written to listenerdown.txt file
below is the batch file
for /f %%i in (computer.txt) do(
qwinsta /server:%%i | findstr/i down >nul 2>&1
if %errorlevel% neq 1
echo %%i >>Listenerdown.txt
)
Move the echo onto the same line as the if statement or else use parentheses to establish scope and %ErrorLevel% will always be 0 because the variable does not get set in a loop without delayed expansion.
setlocal EnableDelayedExpansion
for /f %%i in (computer.txt) do(
qwinsta /server:%%i | findstr /i down >nul 2>&1
if !errorlevel! neq 1 echo %%i>>Listenerdown.txt
)
endlocal
or
setlocal EnableDelayedExpansion
for /f %%i in (computer.txt) do(
qwinsta /server:%%i | findstr /i down >nul 2>&1
if !errorlevel! neq 1 (
echo %%i>>Listenerdown.txt
)
)
endlocal
Cmd.exe parses batch files line by line and unless you tell it that the scope of the command continues onto the next line it will think the command is finished.
You should use setlocal EnableDelayedExpansion, and !errorlevel! instead of %errorlevel%:
#echo off
setlocal EnableDelayedExpansion
for /f %%i in (computer.txt) do (
qwinsta /server:%%i | findstr/i down >nul 2>&1
if !errorlevel! neq 1 echo %%i >>Listenerdown.txt
)
otherwise the value of errorlevel would be expanded only once, before entering the loop, and would not have the correct value. You also have to make sure that echo is in the same row as the if.

Check if command is internal in CMD

I have a command's name and I need to check if this command is internal. How can I do it in a batch script?
So after a lot of tweaking, and thanks to the help of #Andriy M, it finally works.
#ECHO off
CALL :isInternalCommand dir dirInternal
ECHO is dir internal: %dirInternal%
CALL :isInternalCommand find findInternal
ECHO is find internal: %findInternal%
exit /b 0
:isInternalCommand
SETLOCAL
MKDIR %TEMP%\EMPTY_DIR_FOR_TEST > NUL 2>& 1
CD /D %TEMP%\EMPTY_DIR_FOR_TEST
SET PATH=
%~1 /? > NUL 2>&1
IF ERRORLEVEL 9009 (ENDLOCAL
SET "%~2=no"
) ELSE (ENDLOCAL
SET "%~2=yes"
)
GOTO :EOF
OLD SOLUTION
You can use where. If it fails, the command is probably internal. If it succeeds, you get the executable path that proves it's not internal.
C:\Users\user>where path
INFO: Could not find files for the given pattern(s).
C:\Users\user>where find
C:\Windows\System32\find.exe
EDIT: As the comments suggest, this might not be the best solution if you're looking for portability and not just research. So here's another possible solution.
Set %PATH% to nothing so HELP can't find anything and then run HELP on the command you're trying to check.
C:\Users\user>set PATH=
C:\Users\user>path
PATH=(null)
C:\Users\user>%WINDIR%\System32\help del
Deletes one or more files.
DEL [/P] [/F] [/S] [/Q] [/A[[:]attributes]] names
[...]
C:\Users\user>%WINDIR%\System32\help find
'find' is not recognized as an internal or external command,
operable program or batch file.
This might still fail if the command doesn't have help.
EDIT 2: Never mind, this won't work either. Both cases return %ERRORLEVEL%=1.
kichik has a good answer. However, it can give a false positive if there happens to be an executable or batch script within the current directory that matches the supplied command name.
The only way I can think of to avoid that problem is to create a folder that is known to be empty within the %TEMP% directory, and then run the test from that folder.
Here is a modified version of kichik's solution that should work.
#echo off
setlocal
::Print the result to the screen
call :isInternal find
call :isInternal dir
::Save the result to a variable
call :isInternal find resultFind
call :isInternal dir resultDir
set result
exit /b
:isInternal command [rtnVar]
setlocal
set "empty=%temp%\empty%random%"
md "%empty%"
pushd "%empty%"
set path=
>nul 2>nul %1 /?
if errorlevel 9009 (set rtn=not internal) else (set rtn=internal)
popd
rd "%empty%"
(
endlocal
if "%~2" neq "" (set %~2=%rtn%) else echo %1 is %rtn%
)
exit /b 0
Here is a script that will simply list all internal commands, assuming that HELP includes a complete list of internal commands.
Update: Both FOR and IF have special parsing rules that prevent those commands from working if executed via a FOR variable or delayed expansion. I had to rewrite this script to use a CALL and execute the command via a CALL argument instead.
#echo off
setlocal enableDelayedExpansion
set "empty=%temp%\empty%random%"
md "%empty%"
pushd "%empty%"
for /f "delims= " %%A in ('help^|findstr /rc:"^[^ ][^ ]* "') do call :test %%A
popd
rd "%empty%"
exit /b
:test
setlocal
set path=
%1 /? >nul 2>nul
if not errorlevel 9009 echo %1
exit /b 0

Skip when error occurs

i have the following code in batch (cmd):
for /f "delims=" %%f in ('dir /b /s Example') do (
command
if %errorlevel%==1 (
command
SKIP
)
command
)
EDIT:
To make things more clear:
for /f... searches for a directory called 'Example' and loops to search for more directories than one.
the first command is a delete command, it deletes all files in the directory.
the command that happens when an error occurs, is a echo command which writes some info about the error to a text file.
now the hole skip thing; sometimes, the files can't be deleted because of access denied or this file is in use by.... Normally, what would happen if there weren't a skip thing, it would just stop the command and hang. So, what i want to do, is prevent this from happening. Alternatively, i want to use something like skip, so it can skip the file and continue anyways. So i think this command needs to be piped in the delete command.
I hope it's clear now.
Thanks in advance.
Like this?
for /f "delims=" %%f in ('dir /b /s Example') do (
command
if not errorlevel 1 (
command-for-success
) else (
command-for-error
)
)
Create the two command files and run delex.cmd. The files and directories that are not deleted will be logged to delex.txt. The ones that hang, will have a minimized cmd window open that gets killed after a delay by using ping (thanks to Doc Brown's suggestion).
delex2.cmd
----------
#echo off
del /q %1
if exist %1 echo %1 not deleted!>>delex.txt
exit
delex.cmd
---------
#echo off
if exist delex.txt del delex.txt
for /f "delims=" %%f in ('dir /s /b example') do start "delextaskkill" /min delex2.cmd "%%f"
ping 127.0.0.1 -n 3 -w 1000> nul
taskkill /fi "Windowtitle eq delextaskkill"> nul
Tested with:
\example
| file1
| file2
| file3
| file4
| file5
|
\---example
file1
file2
file3
file4
file5
When one uses del, and "access denied" or "this file is in use by..." occurs, %errorlevel% is 0, so testing %errolevel% is useless. Perhaps the following simple solution works in your case:
for /f "delims=" %%f in ('dir /b /s Example') do (
del %%f
if exist %%f (
echo "file not deleted"
) else (
echo "file deleted"
)
)
I believe that this is what you want
for /f "delims=" %%f in ('dir /b /s Example') do (
command
if not errorlevel 1 (
command
) else (
command
goto :eof
)
)
Perhaps this is more advanced than can be accomplished using just built-in cmd processing. Why not consider using the Windows Scripting Host (vbscript/jscript) or even PowerShell? Both will likely provide you the level of control you are requesting.
Try this batch file:
#echo off
REM For each directory named 'Example' ...
for /f "delims=" %%f in ('dir /b /s Example') do (
REM .. enter the directory and delete all the files found there.
pushd .
cd %%f
del /q *
for /f "delims=" %%z in ('dir /b') do (
REM Any files that still exist must have been inaccessable. Log an error.
echo Unable to delete file %%z > C:\logfile.txt
)
popd
)
Tested with the following directory structure:
folder\
|------Example\
| |-------file1 (write-protected)
| |-------file2
| |-------file3
|------deeper\
|-------Example\
|-------file4
|-------file5 (write-protected)
|-------file6
After running the batch file, only file1 and file5 were left. An "Access is denied" message was printed for each write-protected file encountered; if that gets annoying you re-direct the output of the batch file like script.bat > NUL to hide it.
So after all the existing answers didn't satisfy you and you absolutely need a skip within your batch file, i will make another try:
#echo off
setlocal
for /f "delims=" %%f in ('dir /b /s batch') do call :DoSomething %%f
goto end
:DoSomething
echo Here i am: %1
if %errorlevel%==1 goto :eof
echo No error occured
goto :eof
:end
endlocal
The trick is, that the for loop calls a sub function within the same file and gives the needed parameter to it. This new call runs in a new context and can only access the variables which are defined after the do call :DoSomething in the given order their.
So you have to access the variables here with %1, %2, etc. If you want to leave this context you have to make a goto :eof to jump to the end of the file (this marker is predefined in batch mode and should not occur within your file), what leaves the context and returns to the for loop.
After running through the whole loop we just jump to the :end marker, make a little clean up and are finished.
The following looks for the file extentions you want recursively under the "dirname" directory tree and executes commandstuff.bat against that file name:
for /r %i in (c:\dirname\*.ext) do
commandstuff "%i"
Commandstuff.bat looks like this:
#ECHO OFF del %1 IF (%ERRORLEVEL% ==
0) goto END
:ERROR Echo Error deleting %1
:END Echo end
This would run commandstuff.bat for you to delete the files you want. When there is an error it will simply echo the file details and continue processing the next file.

Resources