Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I get the names of 10 randomly-selected files in a folder? I need to do that so I can test and verify our backup and restore policies.

-- CS

SpacerHey, Scripting Guy! AnswerScript Center

Hey, CS. You know, if you’re interested in things that are random, haphazard, arbitrary, or downright chaotic, well, you came to the right place. For example, any time we Scripting Guys have a contest we select the winners via random drawing. When it comes time to write a new Hey, Scripting Guy! column we randomly choose a question to answer. Remember the Butterfly Effect, the idea that a random butterfly flapping its wings in Brazil could be responsible for a blizzard in New England? That was our butterfly!

Note to the people of New England. The Scripting Guys apologize for any inconvenience the blizzard might have caused. We still can’t figure out how that butterfly got out of its jar.

Now, admittedly, making it snow in New England isn’t all that big of a deal; it’s kind of like bragging that you can make it rain in Seattle. So can we do something really challenging, something like randomly grab file names from a folder? Surprisingly enough, it turns out that we can:

strComputer = "."

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

Set colFiles = objWMIService.ExecQuery _
    ("ASSOCIATORS OF {Win32_Directory.Name='C:\Scripts'} Where " _
        & "ResultClass = CIM_DataFile")

intFiles = colFiles.Count

Set objDictionary = CreateObject("Scripting.Dictionary")

intHighNumber = intFiles
intLowNumber = 1

For i = 1 to 10
    x = 0
    Do Until x = 1
        Randomize
        intNumber = Int((intHighNumber - intLowNumber + 1) * Rnd + intLowNumber)
        If objDictionary.Exists(intNumber) Then
            x = 0
        Else
            objDictionary.Add intNumber, intNumber
            x = 1
        End If
    Loop
Next

i = 1

For Each objFile in colFiles
    If objDictionary.Exists(i) Then
        Wscript.Echo objFile.Name
    End If
    i = i + 1
Next

Yes, you’re right: this script is a bit crazy-looking, at least at first glance. So let’s see what we can do to clear up the confusion a little.

We actually start off in straightforward fashion: we simply connect to the WMI service on the local computer (although we could just as easily connect to the WMI service on a remote computer). We then use this line of code to retrieve a collection of all the files found in the folder C:\Scripts:

Set colFiles = objWMIService.ExecQuery _
    ("ASSOCIATORS OF {Win32_Directory.Name='C:\Scripts'} Where " _
        & "ResultClass = CIM_DataFile")

Once we have that collection in tow we then use the value of the Count property to determine the total number of files in that folder, a value we store in a variable named intFiles:

intFiles = colFiles.Count

Why do we need to know the total number of files in the folder? Hang on; we’ll explain that in just a moment. Finally, we create an instance of the Scripting.Dictionary object:

Set objDictionary = CreateObject("Scripting.Dictionary")

Why do we do that? Be patient; sooner or later this will all make sense.

Or so we hope.

We’re now ready to start generating 10 random numbers, numbers we’ll use to indicate the files that we want to grab from the folder. To do that, our first step is to assign values to a pair of variables:

intHighNumber = intFiles
intLowNumber = 1

As you can see, we assign the number of files in the folder (the value of intFiles) to intHighNumber and the value 1 to the variable intLowNumber. Why? Well, we need to pick a random number, and that random number needs to fall between two values: 1 and the total number of files in the folder. For example, if we have 39 files in the folder we need to randomly pick a number between 1 and 39; in a case like that it doesn’t do us any good to pick file number -83 or file number 765. After all, how could we randomly select file 765 if there are only 39 files to begin with?

Note. Yes, we could have used the variable intFiles in the equation, and we could have hard-coded the value 1 into the equation. We used the variables intHighNumber and intLowNumber to help make it clear what those values represent.

That brings us to this big, bad For Next loop:

For i = 1 to 10
    x = 0
    Do Until x = 1
        Randomize
        intNumber = Int((intHighNumber - intLowNumber + 1) * Rnd + intLowNumber)
        If objDictionary.Exists(intNumber) Then
            x = 0
        Else
            objDictionary.Add intNumber, intNumber
            x = 1
        End If
    Loop
Next

What we’re doing here is looping around 10 times; each time through the loop we’ll generate a new random number. Inside the loop the very first thing we do is set the value of a counter variable named x to 0 (and, yes, we’ll explain why in just a second). We then set up a Do Until loop that runs into x = 1. (Don’t panic; we’re getting to that, too.)

Inside the Do Until loop we use these two lines of code to generate a random number between 1 and the total number of files in the folder:

Randomize
intNumber = Int((intHighNumber - intLowNumber + 1) * Rnd + intLowNumber)

Note. We’re not going to explain the random number process in any detail today; for more information, see this not-so-randomly selected Hey, Scripting Guy! column.

We now have a random number (let’s say it’s 13). Because we want to randomly select 10 files, that means we need to generate 10 random numbers. That also means we need to temporarily store our random numbers until we have all 10 of them. We decided that the easiest way to do that is to store all the random numbers in an instance of the Dictionary object.

Of course, there is a catch here: items in the Dictionary must be unique (we can’t store the number 13 multiple times). Because of that we use the Exists method to see if the random number we just generated is already in the Dictionary:

If objDictionary.Exists(intNumber) Then

Let’s say that it is. That means we can’t add the number to the Dictionary again; remember, no duplicate items. Therefore, we set the value of x to 0; because x is 0 that means our Do Until loop will simply run again, and continue to run until we generate a random number that isn’t in the Dictionary. As soon as we find such a number we execute these two lines of code:

objDictionary.Add intNumber, intNumber
x = 1

The first line simply adds the randomly-generated number to the Dictionary (using the number itself as both the Dictionary key and item). After that we set the value of x to 1. Very good; that’s exactly why we do that. As you figured out, our Do Until loop is designed to run until x equals 1. Now that x does equal 1, we’ll break out of the loop and return to the next iteration of the For Next loop. Make sense? Good. This process continues until we have 10 items (numbers) in the Dictionary.

That leaves us with this block of code, which is where we actually grab the 10 file names:

i = 1

For Each objFile in colFiles
    If objDictionary.Exists(i) Then
        Wscript.Echo objFile.Name
    End If
    i = i + 1
Next

The first thing we do here is set the value of the counter variable i to 1; we’ll use this variable to track which file (i.e., file 1, file 2, file 3, etc.) we’re dealing with. (We set i equal to 1 simply because we’ll be starting with the first file in the collection.) After setting up a For Each loop to walk through the collection of files we then encounter this line of code:

If objDictionary.Exists(i) Then

All we’re doing here is checking to see if the value of i (which, the first time through the loop, is equal to 1) is in the Dictionary. What if it is? Well, then that can only mean one thing: 1 is one of our randomly-generated numbers. In turn, we echo back the Name of the first file in the collection. If number 1 is not in the Dictionary then we don’t do anything at all. We only echo back the names of the files whose file number can be found in the Dictionary.

Regardless of whether or not the value exists in the Dictionary we increment i by 1 (making i equal to 2), loop around, and check to see if the value 2 is in the Dictionary. This continues until we’ve walked through the entire collection of files. When all is said and done, we should have a list of 10 randomly-selected file names:

c:\scripts\book1.xls
c:\scripts\employees.log
c:\scripts\imapi.vbs
c:\scripts\read-write.vbs
c:\scripts\tee.txt
c:\scripts\test.csv
c:\scripts\test.txt
c:\scripts\test.txt.doc
c:\scripts\test.xml
c:\scripts\x.txt

Cool.

Hey, that’s OK, CS: no need to thank us. Like we said, we enjoy experimenting with randomness and with Chaos Theory. For example, we recently tried to determine, once and for all, whether a million monkeys typing on a million computers could produce the complete works of Shakespeare. As it turned out, they couldn’t. But at least we did get the latest Sesame Script column out of the deal. Thanks, guys!