Extreme file and folder scripting: Listing a directory tree recursively - Day 2 – Using WMI

BabyScript

 

Now that we've glanced at recursion out of the corner of our eye (on Day 1) and lived, it's time to stare straight-on into the unspeakable horror of the Medusa Code.  If you have sunglasses, please put them on now.  If you try to speak its name, Files-list-recurse-wmi.vbs, we won't be responsible for what might happen to your tongue.

 

strComputer = "."

strDrive = "c:"

strPath = "ff" 'Use double backslashes in path if necessary

strPathAlt = Replace(strPath, "\\", "\")

strName = strDrive & "\" & strPathAlt

Set objWMIService = GetObject("winmgmts:\\" & strComputer)

WScript.Echo strName

Set colFiles = objWMIService.ExecQuery _

 ("SELECT * FROM CIM_DataFile WHERE Drive = '" & strDrive & _

 "' AND Path = '\\" & strPath & "\\'")

For Each objFile in colFiles

  WScript.Echo objFile.Name

Next

Set colFolders = objWMIService.ExecQuery _

 ("ASSOCIATORS OF {Win32_Directory.Name='" & strName & "'} " _

 & "WHERE AssocClass = Win32_Subdirectory " _

 & "ResultRole = PartComponent")

For Each objFolder in colFolders

  WScript.Echo objFolder.Name

  strFolderPath = Replace(objFolder.Path, "\", "\\")

  strFilePath = strFolderPath & objFolder.FileName & "\\"

  Set colFiles = objWMIService.ExecQuery _

   ("SELECT * FROM CIM_DataFile WHERE Drive = '" & strDrive & _

   "' AND Path = '" & strFilePath & "'")

  For Each objFile in colFiles

    WScript.Echo objFile.Name

  Next

  ShowSubFolders objFolder.Name

Next

 

Sub ShowSubFolders(strSubFolder)

Set colSubFolders = objWMIService.ExecQuery _

 ("ASSOCIATORS OF {Win32_Directory.Name='" & strSubFolder & "'} " _

 & "WHERE AssocClass = Win32_Subdirectory " _

 & "ResultRole = PartComponent")

For Each objSubFolder in colSubFolders

  Wscript.Echo objSubFolder.Name

  strSubFolderPath = Replace(objSubFolder.Path, "\", "\\")

  strSubFilePath = strSubFolderPath & objSubFolder.FileName & "\\"

  Set colSubFiles = objWMIService.ExecQuery _

   ("SELECT * FROM CIM_DataFile WHERE Drive = '" & strDrive & _

   "' AND Path = '" & strSubFilePath & "'")

  For Each objSubFile in colSubFiles

    WScript.Echo objSubFile.Name

  Next

  ShowSubFolders objSubFolder.Name

Next

End Sub

 

Here's the output from the same test folder:

 

C:\scripts>files-list-recurse-wmi.vbs

c:\ff

c:\ff\disklog.txt

c:\ff\otherfile.txt

c:\ff\scriptlog.txt

c:\ff\servicelog.txt

c:\ff\servicestatus.txt

c:\ff\testfile.txt

c:\ff\ff1

c:\ff\ff1\disklog.txt

c:\ff\ff1\scriptlog.txt

c:\ff\ff1\ff3

c:\ff\ff1\ff3\disklog.txt

c:\ff\ff1\ff3\scriptlog.txt

c:\ff\ff2

c:\ff\ff2\disk.log

c:\ff\ff2\otherfile.txt

c:\ff\ff2\service.log

c:\ff\ff2\ff4

c:\ff\ff2\ff4\disk.log

c:\ff\ff2\ff4\otherfile.txt

c:\ff\ff2\ff4\service.log

 

Hmm, looks kind of familiar, doesn't it?  You mean, it takes all that code in WMI to produce the same output?  Yep.

 

Actually, this script goes beyond the bonus script in the webcast.  That one only listed folders and subfolders.  This one displays the full Monty, folders, sub-folders and all their files.  But it takes quite a few more lines of code than the FSO version.

 

We start out by assigning the drive and top folder names separately, so we can take them apart later.  WMI lets us (or makes us) define the elements of a file path separately.  Then we'll query the CIM_DataFile class for file instances, filtering with the separate drive and path variables.  Note the lovable quirk of WQL that we have to put double backslashes on both sides of the path.  This gives rise to some of the contortions in this script, as other methods of the class require a path separated by single backslashes rather than double ones.  VBScript is there to help with the Replace function, that lets us pass it a string, a substring to replace and second substring with which to replace the first.  So we use it to replace double backslashes with single backslashes and vice versa.

 

At this point, we've only listed the files in the top folder.  What to do next?  Well, at this point we need a way to get at the subfolders in the current folder, but Win32_Directory doesn't give us anything as handy as the FSO SubFolders property.  What we do have, though, is something called an association class.

 

Now both Greg and Dr. Scripto told me not to talk about association classes, and even Dean had reservations.  That way lies madness, they said.  Association classes are so Byzantine, so baroque, so rococo, that people will run screaming away from scripting and never return.  OK, I said, I’m sure they’ll take it on faith that they just work.

 

Then I tossed and turned that night because I knew I couldn’t do this to our readers.  So here’s what an association class is: a class that associates two other classes or managed resources.  So far, so good.  In this case, the association class is Win32_Subdirectory, which associates two different sets of instances of Win32_Directory.  One of the two Win32_Directory instances is called GroupComponent, and it represents the parent directory of the two related instances.  The other is called PartComponent, and it represents the subordinate directory or directories in the relationship.

 

So when this ASSOCIATORS OF query:

"ASSOCIATORS OF {Win32_Directory.Name='" & strName & "'} " _

 & "WHERE AssocClass = Win32_Subdirectory " _

 & "ResultRole = PartComponent"

filters for "ResultRole = PartComponent", it's asking for the directories that are subdirectories of the directory specified by strName.  The query returns a collection of these associated subdirectories, which we assign to an object reference, colSubFolders.

 

If you want to pursue this, the WMI SDK documentation for Win32_Subdirectory is at:

https://msdn.microsoft.com/library/en-us/wmisdk/wmi/win32\_subdirectory.asp

 

Besides using the keywords “ASSOCIATORS OF” in a WQL query, as this script does, you can also call the AssociatorsOf method of the SwbemServices object, which is the kind of object referenced by objWMIService in this line:

Set objWMIService = GetObject("winmgmts:\\" & strComputer)

Then when you call objWMIService.AssociatorsOf(parameters), the only required parameter (there are also several optional ones including association class) is the object path of a managed resource or resources, such as:

"Win32_Service.Name='Alerter'"

 

Other association classes work in slightly different ways, but all enable you to associate related instances of classes.  For a fuller explanation of association classes, see:

 

Have you run screaming yet?  If not, you've become one of the few, the proud, the ASSOCIATORS.  We're working on a secret handshake, but you can tell members of the confraternity by the glazed look in their eyes.

 

But we're not quite done yet.  Once we've used the association class to get the subfolders, we still have to loop through the collection and then, as with FSO, the subroutine has to call itself recursively to loop through any subfolders of the subfolders.

 

I hope it goes without saying that if you wanted to perform this astounding feat on a remote machine, you could just change line 1 to:

            strComputer = "computer1"

where computer1 is an accessible host on your network on which you have administrative privileges.

 

So we've done it.  We've actually walked a directory tree using both FileSystemObject and WMI.  Let the high-fiving and end-zone dances (or, for those of the soccer persuasion, "Gooooooooool" celebration) begin.

 

I know you're thinking to yourselves, but you're too polite to ask: Um, couldn't you do all this in one line with dir /s?  Well, actually, um, I guess so (Dr. Scripto, help me out here!).  But then we wouldn't have had all this fun.  Nor would we appreciate what dir has to do under the hood.  If you ever wanted to customize this or add some functionality that does something to or within the resulting directory tree, you wouldn't know how.  And, most important, we would not have achieved such a deep understanding of extreme file and folder scripting with both FSO and WMI.