15

I'm writing a batch (.bat) script and I need to handle the case in which the deletion of a folder fails. I'm using %errorlevel% to catch the exit code, but in the case of the rd command it seems not to work:

C:\Users\edo\Desktop>rd testdir
Directory is not empty

C:\Users\edo\Desktop>echo %errorlevel%
0

Why? What do you suggest?

underscore_d
  • 6,309
  • 3
  • 38
  • 64
etuardu
  • 5,066
  • 3
  • 46
  • 58

2 Answers2

28

Wow, this is the 2nd case I've seen where ERRORLEVEL is not set properly! See File redirection in Windows and %errorlevel%.

The solution is the same as for detecting redirection failure. Use the || operator to take action upon failure.

rd testdir || echo The command failed!

The bizarre thing is, when you use the || operator, the ERRORLEVEL is then set properly to 145 if the folder was not empty, or 2 if the folder did not exist. So you don't even need to do anything. You could conditionally "execute" a remark, and the errorlevel will then be set properly.

rd testdir || rem
echo %errorlevel%

I thought the above gave a complete picture. But then a series of comments below demonstrated there are still potential problems when /RD /S is used. If a file or subfolder under the parent folder is locked (at any level under parent) then RD /S /Q PARENT && echo removed || echo failed will print out an error message, but the && branch fires instead of the || branch. Very unfortunate. If the command fails because the parent folder itself is locked, then || will properly fire and set the ERRORLEVEL.

It is possible to detect failure in all cases by swapping stderr with stdout and piping the result to FINDSTR "^". If a match is found, then there must have been an error.

3>&2 2>&1 1>&3 rd /s test | findstr "^" && echo FAILED

The swap of stderr and stdout is important when /q is missing because it allows the "Are you sure (Y/N)?" prompt to be visible on stderr, separate from the error message on stdout.

dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Well, that just worked. I suppose the problem is related to `%errorlevel%` and has nothing to do with `rd`. I think I should rewrite my error handlings using this structure for a more deterministic behaviour. Thanks! – etuardu Jun 21 '12 at 12:14
  • 4
    This works fine for codes 2 and 145, but in case of "Access denied" or "The process cannot access the file because it is being used by another process" then it just leaves ERRORLEVEL unchanged. :( – Andreas Vergison Apr 07 '15 at 11:26
  • @AndreasVergison - Thanks! I updated my answer with your info. – dbenham Apr 07 '15 at 14:44
  • 1
    helped me out! Thank you... http://itados.blogspot.co.at/2015/04/dos-error-levels.html – Daniel Kienböck Apr 21 '15 at 00:26
  • I just tried it on windows 10. I have the same problem @AndreasVergison has. I don't get an errorlevel (or more accurately, I get "OK") even though it fails to delete because "the file is being used by another process". I get the error message in the console but it doesn't have an ErrorLevel – Mark Mar 14 '17 at 16:51
  • @Mark - Hmmm, I just double checked on my Win 10 machine and the `||` solution works fine for me, with the expected ERRORLEVEL of 32. The simplest syntax to get the correct result is `rd "yourFolder" || rem`, and then on the next line `echo %errorlevel%`. But if that code is within a parenthesized block, then you must enable delayed expansion and use `echo !errorlevel!` instead. – dbenham Mar 22 '17 at 11:30
  • 1
    This does not work with parameter /s for me. Can someone confirm this? – Stefan Jun 27 '17 at 13:01
  • @Stefan I just tried on Windows 7 and this worked for me: `(call ) & rd /s aaaa && echo. || rem` I got both the In Use message as well as errorlevel being properly set. Could you be more specific about exactly what syntax didn't work, and what OS you're using? – Barniferous Jun 28 '17 at 15:10
  • 1
    @Barniferous, it depends on the error. I can confirm @Stefan's findings. I.e., `rd /q /s tmp && echo OK || echo ERROR !errorlevel!` will print _"The process cannot access the file because it is being used by another process."_ and then "OK" (no errorlevel). Other errors, like the ERROR 2 from dbenham's post are reported correctly, though. Repro: open command prompt and `cd` to a dir. In another command prompt, try to delete that dir with dbenham's solution. No errorlevel, but error is printed... :S. Tested on Windows 7. – Abel Oct 29 '17 at 16:28
  • 1
    Conditional Execution `||` doesn't work, at all, if an internal folder is blocked for an access `Access is denied`. The `RD` just have `0` exit code. – it3xl Sep 16 '18 at 13:11
  • 1
    I think the above mentioned example from @dbenham worked for "Access Denied" case only because it was a non-recursive removal. It was an "Access denied" about "junk" directory itself and not about its internals. If the "Access denied" is caused by some file inside the directory then ERRORLEVEL is 0. I think this is may be because of the buggy function from "del" (https://stackoverflow.com/questions/22953027/batch-file-and-del-errorlevel-0-issue). Maybe during the recursive removal Windows tries to use "del" for each file inside the directory and it always returns 0 unfortunately. – Alexander Samoylov Nov 16 '18 at 15:59
  • @AlexanderSamoylov - Great analysis. I did some more tests, and you are absolutely correct. I'll update my answer when I get a chance. – dbenham Nov 16 '18 at 17:42
  • I am reminded of the behavior of POSIX `rm -f` (forcibly remove), which not only disregards permissions to the extent possible, but also suppresses error codes, always exiting 0. I think this is to make clean up actions as robust as possible, especially when running with `set -e`, as in a Makefile recipe. (Obviously, in a recipe, a minus prefixed to a command line achieves the same result). Perhaps `cmd`'s treatment of `rd` and `del` is motivated similarly. – user1254127 Nov 02 '21 at 21:37
10

rd does not set errorlevel to zero - it leaves errorlevel intact: f.e. if previous operation ends in positive errorlevel and rd finishes successfully it leaves errorlevel unchanged. Example: error levels of robocopy below 4 are warnings and not errors and can be ignored so the following code may end with error even when the directory was deleted successfully:

robocopy ...
if errorlevel 4 goto :error
rd somedir
if errorlevel 1 goto :error

Solution: ignore the error and check if the directory still exists after rd:

rd somedir
if exist somedir goto :error
rychu
  • 896
  • 1
  • 7
  • 16
  • 2
    Also about `robocopy`: be sure **not** to reset the errorlevel with `set errorlevel=0` after you check it's not >= 4 because this command creates an environment variable that permanently overwrites the internal errorlevel. Read [here](http://blogs.msdn.com/b/oldnewthing/archive/2008/09/26/8965755.aspx) – rychu Apr 10 '15 at 13:44
  • This is the best solution IMO. Instead of trying to do convoluted workarounds, swapping outputs, and other crap trying to detect all the odd cases... just see whether the directory is still there; if it is, you had a problem. – Doktor J May 31 '19 at 17:02