Hey Scripting Guy! How can I delete a folder and all its subfolders?
— FB
Hey, FB. You know, a lot of people think the Scripting Guys know everything there is to know about scripting. Let’s put it this way: we wish. In fact, it’s only when we begin to think that we do know something about scripting that we get ourselves into trouble.
Take (please!) this seemingly-simple little question about deleting a folder and all its subfolders. One of the Scripting Guys (Greg asked us not to mention his name, to save him from embarrassment) thought this was easy; in fact, he already had a script that could delete a folder and its subfolders:
strComputer = “.” Set objWMIService = GetObject(“winmgmts: \\” & strComputer & “\root\cimv2”)Set colFolders = objWMIService.ExecQuery _ (“Select * from Win32_Directory where Name = ‘c:\\Scripts'”)
For Each objFolder in colFolders errResults = objFolder.Delete Next
Our unnamed Scripting Guy grabbed the script, did a quick test on his Windows XP Service Pack 1 computer and on a Windows 2000 computer, and discovered that the script worked just fine. With yet another job well done, he then took the rest of the day off and began planning how he would spend the huge bonus he would undoubtedly receive for having successfully solved this problem.
It was only later that another Scripting Guy (and – alas – the newest member of the team at that) pointed out that his script – which worked perfectly well back in the Dark Ages of computing – no longer functioned in the modern world; in particular, it failed on any Windows XP computer running Service Pack 2. Why? Well, in Service Pack 1 you delete a folder and all its contents, including any subfolders, with no questions asked. In Service Pack 2, however, the behavior of the Win32_Directory class has changed. On a computer running SP2, a folder cannot be deleted if it contains even a single subfolder; instead, you generate a “Folder not empty” error. Ouch! In fact, using WMI the only way to delete a folder in SP2 is to first delete all its subfolders. And, of course, before you can delete a subfolder you have to delete all of its subfolders. And before you can delete any of those – well, it just goes on and on from there.
Of course a lesser person would have given up at that point, and just told you that, “There’s no way to delete a folder and all its contents.” And that’s exactly what our anonymous Scripting Guy was going to do. However, under intense pressure from his colleagues, he came up with this solution instead, one that creates a list of the entire folder tree and then methodically deletes folders from the bottom to the top. And don’t worry, we’ll tell you what that all means in a minute.
Here’s a script that ought to work on any version of Windows (or at least any version of Windows with WMI installed):
Dim arrFolders() intSize = 0strComputer = “.” Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)
strFolderName = “c:\scripts\archive”
Set colSubfolders = objWMIService.ExecQuery _ (“Associators of {Win32_Directory.Name='” & strFolderName & “‘} ” _ & “Where AssocClass = Win32_Subdirectory ” _ & “ResultRole = PartComponent”)
ReDim Preserve arrFolders(intSize) arrFolders(intSize) = strFolderName intSize = intSize + 1
For Each objFolder in colSubfolders GetSubFolders strFolderName Next
Sub GetSubFolders(strFolderName) Set colSubfolders2 = objWMIService.ExecQuery _ (“Associators of {Win32_Directory.Name='” & strFolderName & “‘} ” _ & “Where AssocClass = Win32_Subdirectory ” _ & “ResultRole = PartComponent”)
For Each objFolder2 in colSubfolders2 strFolderName = objFolder2.Name ReDim Preserve arrFolders(intSize) arrFolders(intSize) = strFolderName intSize = intSize + 1 GetSubFolders strFolderName Next End Sub
For i = Ubound(arrFolders) to 0 Step -1 strFolder = arrFolders(i) strFolder = Replace(strFolder, “\”, “\\”) Set colFolders = objWMIService.ExecQuery _ (“Select * from Win32_Directory where Name = ‘” & strFolder & “‘”)
For Each objFolder in colFolders errResults = objFolder.Delete Next Next
We won’t even attempt to walk through this code line-by-line; instead we’ll just give you a rough idea of what we’re doing, and why, and point you to some additional resources if you need more information.
To begin with, let’s say C:\Scripts\Archive looks like this:
We can’t delete the Archive folder right away; that’s because of Subfolder A1 and Subfolder A2. And we can’t delete Subfolder A1 right away; that’s because of Subfolder B1. To get rid of the Archive folder we need to:
• |
Delete Subfolder A2. |
• |
Delete Subfolder B1, clearing out Subfolder A1. |
• |
Delete Subfolder A1. With both Subfolder A1 and Subfolder A2 gone, we’ll then be free to delete the Archive folder. |
• |
Delete the Archive folder. |
The approach we’ve taken makes use of a recursive function; this function can identify all the subfolders of a folder, as well as all the subfolders of those subfolders. We won’t discuss this function in any detail today; for an explanation of how it works, check out this Hey, Scripting Guy! column.
By necessity, our recursive function starts with the first folder – Archive – and then works its way down the tree. Unfortunately, that’s a bit of a problem; after all, we need to start deleting folders from the bottom of the tree and work our way up. There are probably lots of ways to handle this problem, but what we did was elect not to try deleting folders right away; instead, all we do is stash the folder paths into an array named arrFolders. Each time we encounter a new folder or subfolder we add the folder path to the array. When the recursive function finishes, our array will look like this:
C:\Scripts\Archive C:\Scripts\Archive\Subfolder A1 C:\Scripts\Archive\Subfolder A1\Subfolder B1 C:\Scripts\Archive\Subfolder A2
Note. We used a so-called “dynamic” array in this script; for more information about dynamic arrays – and about weird-looking commands like Redim Preserve – please see this portion of the Microsoft Windows 2000 Scripting Guide. |
Trust us: we’re well aware that this was way easier in Windows 2000. But at least now we’re ready to start deleting folders. If you look at the items in our array, they happen to be exactly opposite of what we need; for example, the Archive folder is the first item in our array, but it has to be the last item that we delete. If we could invert the array – making the first last and the last first, to steal a phrase – we’d be in business. That’s where this block of code comes in:
For i = Ubound(arrFolders) to 0 Step -1 strFolder = arrFolders(i) strFolder = Replace(strFolder, “\”, “\\”) Set colFolders = objWMIService.ExecQuery _ (“Select * from Win32_Directory where Name = ‘” & strFolder & “‘”)For Each objFolder in colFolders errResults = objFolder.Delete Next Next
What we’re doing here is reading our array from the bottom up. We’re creating a loop that starts with the very last item in the array; that’s what the Ubound (upper bound) function is for. We’re then going to work our way down to the first item in the array: item 0. (As you might recall, the first element in an array is always item 0, not item 1.) The Step -1 function just means that we step backwards rather than forward: instead of going 0-1-2-3, we’re going 3-2-1-0. This is how we can start deleting at the bottom of the tree.
Of course, before we can do that we need to adjust the folder paths; that’s what we do here:
strFolder = Replace(strFolder, “\”, “\\”)
Our folder paths are going to look like this: C:\Scripts\Archive\Subfolder A1\Subfolder B1. That’s fine, except that we need to include these paths in a WQL query. Consequently, we need to double up all the \’s, resulting in paths that look like this: C:\\Scripts\\Archive\\Subfolder A1\\Subfolder B1. We use the Replace function to replace each \ with \\.
You’re right: there’s nothing straightforward about this script, is there?
With our new \\ folder paths we can then use WMI to connect to the folder in question and – at long last – use the Delete method to actually delete the folder. That’s what happens here:
Set colFolders = objWMIService.ExecQuery _ (“Select * from Win32_Directory where Name = ‘” & strFolder & “‘”)For Each objFolder in colFolders errResults = objFolder.Delete Next
Having disposed of Subfolder B1, we can then start working our way up the tree, eventually deleting C:\Scripts\Archive.
Five million lines of code later, we’re done, and C:\Scripts\Archive has been deleted. We’re not saying it’s a particularly elegant solution, but it is a solution, and it will work on all versions of Windows. And because it uses WMI, it works equally well on remote machines as it does on the local computer.
So there you have it: deleting a folder and all its subfolders, a subject we’ve vowed never to deal with again. Well, at least not until the next service pack comes out ….
0 comments