Hey, Scripting Guy! Question

Hey, Scripting Guy! I have a script that reads through a text file and then checks a folder to see if all the subfolder names listed in that text file actually exist. (For example, if the parent folder is C:\Scripts the script might check to see if a folder named Test Folder – that is, C:\Scripts\Test Folder – actually exists.) This part of the script works pretty well. However, sometimes a folder exists that isn’t listed in the text file. When that happens, I’d like to be prompted with a message box that would let me decide whether or not I wanted the script to add that folder to the text file. How do I do that?

-- SW

SpacerHey, Scripting Guy! AnswerScript Center

Hey, SW. We know that many people read this column because they want to know if today will be the day when the Scripting Guy who writes this column finally goes too far and writes something that gets him fired.

Note. If you’re one of those people we can save you the trouble of reading the rest of this column; the answer is “No, not today.” But you might want to circle February 8th on your calendar. As it turns out, the Scripting Guys have a meeting scheduled for that afternoon, a meeting that could easily push the Scripting Guy who writes this column over the edge.

Of course, other people read this column because they want the inside scoop on what’s going on in the scripting world. What kind of a scoop? Well, how about this one: although we haven’t officially announced this yet, the 2008 Winter Scripting Games (February 15th through March 3rd) will include several new challenges in addition to the “classic” events.

Note. Why haven’t we officially announced this yet? Well, let’s just say that one of the two Scripting Guys is a little bit behind in his work, and let it go at that.

And no, surprisingly enough, this time it’s not Scripting Guy Jean Ross.

At any rate, one new challenge is the User Group Challenge, sponsored by TechNet Magazine. When you compete in the Games this year, you’ll have the option to indicate the name of a Microsoft user group that you belong to. Why would you want to do that? Well, the user group that ends up with the most participants (on a percentage basis) will be declared the winner of the User Group Challenge. Big deal? Well, maybe; after all, the winning user group will be awarded a trophy; the winning user group will be featured in a Script Center article; and each member of the group that competes in the Games will be given an individual award (of a to-be-determined nature). That’s kind of a big deal, wouldn’t you say?

And yes, of course you can cheat. If you want to get together as a group and tackle the Scripting Games events, well, who are we to tell you no? Feel free to work together on your scripts; that’s what user groups are for, right? Just remember, however, that if a dozen of you work together on the scripts all 12 of you must submit separate entries. Otherwise you’ll get credit for just 1 entry, and only the person who does the actual submitting will be eligible for all the individual prizes. And that would be no fun at all, especially if you’ve seen the list of prizes.

Now, admittedly, not everyone belongs to a user group. However, most people do have some allegiance to a country, either because they live there, they were born there, or maybe because that country has really good food. If you do have an allegiance of some kind to a country of some kind then you might be interested in the International Challenge. In the previous two Scripting Games we’ve given people the option of competing for a country of their choice, and we’ve tallied scores on a per-country basis. This year, however, we’re going one step further: this year the country that has the most entrants (based on the population of that country) will be crowned the International Challenge winner. Even better, during the month of April we’re going to dedicate the entire Script Center to that country. We’re not sure what that means (although we are sure that TechNet will freak out when they hear about this) but it will be cool. We promise.

And sure, you can cheat on this one, too: if all the residents of Finland want to get together to work on solutions for the Games, well, the Scripting Guys can’t stop you. (Maybe we could stop some other country, but definitely not Finland.) So what the heck: recruit your compatriots, and show everyone where the best scripters in the world really hail from.

Or at least say they hail from. To tell you the truth, we still haven’t been able to find Targetopia, VandyLand, or Bmraarykduesn or some of the other countries from the 2007 Scripting Games on any map.

And then there was one: the Sudden Death Challenge. This name is actually left over from another idea we had and then decided not to follow up on. To be perfectly honest the Sudden Death Challenge has nothing to do with sudden death; we just liked the name so we decided to keep it. (Editor’s Note: Something else TechNet won’t be very happy about, not to mention the politically-correct-terminology people here at Microsoft. Don’t be surprised if this name changes at some point in the midst of all this.) With the Sudden Death challenge, we’ll post a sort of mini-event on the opening day of the Games; this event might require you to write a little script, solve a puzzle, or simply know a little something about scripting. The first mini-event will be available for only a day or two, then will be replaced by another mini-event (there will be 10 of these in all). The idea is that you come back every day or two, try your hand at a mini-event, and see how many total Sudden Death points you can accumulate. When the Games are over, we’ll tally the points and award … something … to the winner. (Budgets being what they are around here, we haven’t been able to finalize the prize list yet.)

There you have it, folks: the 2008 Winter Scripting Games, coming February 15th to your favorite Script Center.

Um, yes, that would be this Script Center.

Of course, that does leave you with at least one problem: how are you going to pass the time between now and February 15th? Well, here’s an idea; maybe you can try running the following script, a script that can tell you whether a text file of folder names and a folder full of subfolders are in synch:

Const ForReading = 1
Const ForAppending = 8 
Const FOLDER_PATH = ("C:\Scripts\") 

Set objFSO = CreateObject("Scripting.FileSystemObject") 
Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForReading)

Set objFolderList = CreateObject("Scripting.Dictionary")
objFolderList.CompareMode = vbTextCompare

Do Until objFile.AtEndOfStream 
    strName = objFile.ReadLine 
    objFolderList.Add strName, strName
Loop

objFile.Close
 
Set objExistingFolders = CreateObject("Scripting.Dictionary")
objExistingFolders.CompareMode = vbTextCompare

strComputer = "."

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

Set colSubfolders = objWMIService.ExecQuery _
    ("Associators of {Win32_Directory.Name='C:\Scripts'} " _
        & "Where AssocClass = Win32_Subdirectory " _
            & "ResultRole = PartComponent")

For Each objSubfolder in colSubfolders
    strName = objSubfolder.FileName
    objExistingFolders.Add strName, strName
Next

For Each strKey in objFolderList.Keys
    strFolderName = strKey
    If Not objExistingFolders.Exists(strFolderName) Then
        Wscript.Echo "The folder " & strFolderName & " does not exist."
    End If
Next

For Each strKey in objExistingFolders.Keys
    strFolderName = strKey
    If Not objFolderList.Exists(strFolderName) Then
        strMessage = "The folder " & strFolderName & " is not in the text file. "
        strMessage = strMessage & "Would you like to add this folder to the text file?"
        intReturn = Msgbox(strMessage, vbYesNo, "Add to Text File")
        If intReturn = vbYes Then
            strNewItems = strNewItems & strFolderName & vbCrLf
        End If
    End If
Next

If strNewItems <> "" Then
    Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForAppending)
    objFile.Write strNewItems
    objFile.Close
End If

Admittedly, this is kind of a long script (at least for a Hey, Scripting Guy! column) so maybe we better get down to business and see if we can explain how the thing works.

As you can see, we start out in pretty straightforward fashion, defining three constants: ForReading and For Appending (which we’ll need when reading from and potentially writing to the text file); and FOLDER_PATH, a constant we copied out of SW’s original script that holds the path for the folder to be checked for subfolders. In this case, that’s C:\Scripts\.

Note. Yes, the \ on the end of the path is important; by including the \ we can determine complete folder paths for any subfolders simply by tacking the folder name on to the end of this value.

After defining the constants we create an instance of the Scripting.Dictionary object, then use the OpenTextFile method to open the file C:\Scripts\Test.txt for reading:

Set objFSO = CreateObject("Scripting.FileSystemObject") 
Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForReading)

And then we go ahead and read the – wait, hold on a second: this time we don’t just go ahead and read the file. Instead, we create an instance of the Scripting.Dictionary object, an instance we give the object reference objFolderList:

Set objFolderList = CreateObject("Scripting.Dictionary")

Why do we need an instance of the Dictionary object? Well, to tell you the truth, we don’t; we need two instances of the Dictionary object. Our task here, as you know, consists of two parts: first we need to read through a text file and see if the folders listed in that file really exist, then we need to loop through a collection of subfolders and see if those folders are listed in the text file. Although there are several different ways we could approach this problem, we decided the easiest solution was to put the folder names in the text file into one Dictionary object, then put the actual subfolder names into another Dictionary object. Thanks to the Dictionary object’s Exists method, we can then easily see if the items in Dictionary 1 also appear in Dictionary 2, and vice-versa.

As you’re about to see.

Incidentally, we also set the Dictionary’s CompareMode property to the VBScript constant vbCompareText. That ensures that our dictionaries do case-insensitive comparisons; in other words, this makes sure that a folder named Test Folder and a folder named test folder are considered one and the same.

So how do we get folder names out of the text file and into the Dictionary? Like this:

Do Until objFile.AtEndOfStream 
    strName = objFile.ReadLine 
    objFolderList.Add strName, strName
Loop

All we’ve done here is set up a Do Until loop that runs until we reach the end of the text file (that is, until the file’s AtEndOfStream property is true). Inside the loop, we use the ReadLine method to read in the first folder name, then use the Add method to add that folder name to the Dictionary:

strName = objFile.ReadLine 
objFolderList.Add strName, strName

As soon as all the folders have been added to the Dictionary we then call the Close method and close the text file.

One Dictionary down, one Dictionary to go.

To take care of Dictionary 2, we start out by creating another instance of the Scripting.Dictionary object, giving this instance the object reference objExistingFolders:

Set objExistingFolders = CreateObject("Scripting.Dictionary")

Now it’s time to retrieve the subfolders of C:\Scripts. In his original script, SW used the FileSystemObject to retrieve the collection of subfolders. That works great; the only drawback is that it pretty much limits you to running the script on the local computer. If you want to run that script against a remote computer, well, then you have some additional matters to work out. The Scripting Guys don’t like having additional matters to work out; therefore, we decided to use WMI to retrieve all the subfolders of C:\Scripts:

strComputer = "."

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

Set colSubfolders = objWMIService.ExecQuery _
    ("Associators of {Win32_Directory.Name='C:\Scripts'} " _
        & "Where AssocClass = Win32_Subdirectory " _
            & "ResultRole = PartComponent")

Note. OK, you’re right: that block of code also is designed to work against the local computer. But that’s no big deal; if you want to retrieve this information from a remote machine then simply assign the name of that computer to the variable strComputer. In other words:

strComputer = "atl-fs-001"

From here we can populate our second Dictionary object simply by looping through the collection of subfolders, retrieving the value of the FileName property, and then adding that value to the Dictionary:

For Each objSubfolder in colSubfolders
    strName = objSubfolder.FileName
    objExistingFolders.Add strName, strName
Next

That’s pretty cool: we now have two fully-populated Dictionary objects. The only question remaining is this: what exactly are we going to do with those Dictionary objects? Well, for starters, we’re going to do this:

For Each strKey in objFolderList.Keys
    strFolderName = strKey
    If Not objExistingFolders.Exists(strFolderName) Then
        Wscript.Echo "The folder " & strFolderName & " does not exist."
    End If
Next

What we’re doing here is looping through the keys in our first Dictionary object, the one containing the folder names (just the names, not the complete paths) that we read in from the text file. For each key we store the key name in a variable named strFolderName, then use the Exists method to see if that same value exists in our other Dictionary, the one containing the actual subfolders found in C:\Scripts. If the value does not exist in the second Dictionary that means that this particular folder is listed in the text file, but doesn’t actually exist. Hence we echo back a message to that effect:

The folder Test Folder does not exist.

This, by the way, is what SW’s original script did.

We then do something similar with our second Dictionary checking to see if all the actual subfolders of C:\Scripts are listed in the text file:

For Each strKey in objExistingFolders.Keys
    strFolderName = strKey
    If Not objFolderList.Exists(strFolderName) Then
        strMessage = "The folder " & strFolderName & " is not in the text file. "
        strMessage = strMessage & "Would you like to add this folder to the text file?"
        intReturn = Msgbox(strMessage, vbYesNo, "Add to Text File")
        If intReturn = vbYes Then
            strNewItems = strNewItems & strFolderName & vbCrLf
        End If
    End If
Next

Per SW’s wishes, we do a couple things if the subfolder is not listed in the text file. To begin with, we use these two lines of code to construct a message informing the user that the folder is not in the text file, then asking if he or she would like the script to add the folder name to that file:

strMessage = "The folder " & strFolderName & " is not in the text file. "
strMessage = strMessage & "Would you like to add this folder to the text file?"

We then use this line of code to display a message box containing a Yes button and a No button (the VBScript constant vbYesNo):

intReturn = Msgbox(strMessage, vbYesNo, "Add to Text File")

If the user clicks Yes (that is, If intReturn = vbYes) we append the folder name and a carriage return-linefeed (vbCrLf) to a variable named strNewItems. If the user clicks No, then we simply loop around and repeat the process with the next key in the Dictionary.

When we’ve finished looping through all the keys in the second Dictionary, we check to see if the strNewItems is anything but an empty string:

If strNewItems <> "" Then

If that’s true, that means that we have new folder names to add to our text file. Consequently, we execute these three lines of code:

Set objFile = objFSO.OpenTextFile("C:\Scripts\Test.txt", ForAppending)
objFile.Write strNewItems
objFile.Close

Here we’re simply opening our text file (C:\Scripts\Test.txt) for appending. We call the Write method to add the value of strNewItems to the end of the file, then call the Close method to close the text file once and for all. And that, as they say in movie business, is a wrap.

We hope that takes care of your problem, SW. Incidentally, if any of you are still not sure whether or not you’ll enter this year’s Scripting Games, well, consider this email we recently received from an old friend of ours:

“We are looking forward to the Games again this year. We’ve already started preparing for them by drinking heavily and polishing our bobbleheads.”

There you have it: drinking heavily and polishing their bobbleheads. And remember, those guys are simply training for the Games. Imagine how much fun they’ll have once the Games actually begin. See you all February 15th.