2008 Winter Scripting Games

Solution to Beginner VBScript Event 1: Pairing Off

Event 7 Solution


VBScript solution to Event 7 in the 2008 Winter Scripting Games.

Solutions are also available for Windows PowerShell and Perl.

*

Event 7 – Squashing Bug

This event tested your debugging skills. In the process, you needed to learn a little bit about how subroutines work, and a little bit about recursion, one of the more confusing concepts in scripting. Were you able to start with a script that’s completely broken and make it work, without starting from scratch? The script we started with was supposed to do the following:

Find all the text files (files with a .txt file extension) in the C:\Scripts folder and its subfolders that were created more than 10 days ago.

Copy all those files to the C:\old folder.

Echo the names of all the files that were copied.

Echo the total number of files that were copied.

Let’s start by taking a look at the broken script, then we’ll tell you what needed to be done to fix it:

Set objFSO = CreateObject("FileSystemObject")
Set objFolder = objFSO.GetFolder("C:\scripts")

dtmOld = DateAdd("m", -10, Now)
 
AllFolders objFolder

CopyTextFiles objFolder

Sub AllFolders Folder
    For Each objFolder in Folder.SubFolders

        CopyTextFiles subfolder
        AllFolders Subfolder

    Next
End Sub


Sub CopyTextFiles(subFolder)

    colFiles = subFolder.Files

    For Each objFile in colFiles
        arrSplitName = Split(objFile.Name, ".")
        strExtension = arrSplitName(UBound(arrSplitName) - 1)
        If strExtension = "txt" and objFile.DateCreated > dtmOld Then
            objFSO.CopyFile objFile.Path, "C:\old\"
            Wscript.Echo objFile.Name
            i = i + 1
        Edn If
    Next

End

Wscript.Echo
Wscript.Echo "Total Files: " & i

We mentioned in the instructions that we had introduced 10 errors into this script. We’ll give you the list first, then walk through them one-by-one:

1.

Add Set to line 1 in CopyTextFiles

2.

Change objFolder in AllFolders to subFolder

3.

Initialize i outside sub

4.

Remove "- 1" from the UBound function call

5.

Change "m" to "d" in DateAdd

6.

Fix typo in End If

7.

Add Sub to End Sub

8.

Change > to <

9.

Add parentheses to AllFolders

10.

Add "Scripting." to FileSystemObject

Add Set to Line 1 in CopyTextFiles

The first line in the CopyTextFiles subroutine is this:

colFiles = subFolder.Files

If you look at our very first call to CopyTextFiles, you’ll see that we pass a Folder object in as the parameter:

CopyTextFiles objFolder

This means that within CopyTextFiles, the subFolder variable is a Folder object. In the statement in question we’re using the Folder object to get a collection of File objects. Any time you assign an object, including a collection of objects, to a variable, you need to use the Set statement. Our corrected line looks like this:

Set colFiles = subFolder.Files

Change objFolder in AllFolders to subFolder

Take a look at the For Each loop within our AllFolders subroutine:

For Each objFolder in Folder.SubFolders

    CopyTextFiles subfolder
    AllFolders Subfolder

Next

This loop assigns each instance within Folder.SubFolders to the variable objFolder. That’s fine, except now look at the two lines within the loop:

CopyTextFiles subfolder
    AllFolders Subfolder

These two lines call the subroutines CopyTextFiles and AllFolders. Both of these subroutines take one parameter, and for both we’re passing in the variable subFolder (remember, VBScript is not case-sensitive). Wait a minute, where did subFolder come from? And where are we using objFolder? Well, there’s the mistake. We’re looping through instances of the subfolders so we can do something with them, which means we need to either change objFolder to subFolder or change both instances of subFolder to objFolder. We went with the former:

For Each Subfolder in Folder.SubFolders

    CopyTextFiles subfolder
    AllFolders Subfolder

Next

Initialize i Outside the CopyTextFiles Subroutine

One of the things we need to do is echo back the number of files that are copied. Inside the CopyTextFiles subroutine, we increment the counter i each time we copy a file. At the very end of the script, when we’re all done, we echo the value of i. And what comes back? This:

Total Files:

That’s right, nothing. The reason for this has to do with the way variables work in subroutines. If you use a variable inside a subroutine, that variable exists only in the subroutine. As soon as you leave the subroutine the variable no longer exists. This is called the “scope” of the variable. Variables can have “global scope” or “local scope.” Global scope means that the variable is declared in the main part of the script, outside any subroutines or functions, and so it can be used anywhere and its value will be the same no matter where it’s referenced in the script. A variable with local scope exists only in the subroutine or function where it’s used. If you try to access that same variable anywhere else, such as another subroutine, it will be considered a new variable and won’t reference the same value.

That’s a very long-winded way of saying that in order for the value of i to stay the same after you leave the subroutine, you have to initialize it outside the subroutine. We went ahead and initialized it at the very beginning of our script:

i = 0

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder("C:\scripts")
…

Remove "- 1" from the UBound Function Call

As we read through our list of files for each folder, our broken script runs these lines of code:

arrSplitName = Split(objFile.Name, ".")
strExtension = arrSplitName(UBound(arrSplitName) - 1)

In these lines we’re looking for the file extension of each file so we can determine whether it’s a text file (in other words, it has a txt file extension). To find the file extension we call the Split function on the file name, splitting on the dot (.) that separates the name from the extension. That will give us an array (arrSplitName) that contains two elements: the file name and the file extension. Well, we should say it probably give us two elements. It’s possible to have a filename with multiple dots in it, such as Test.File.02.10.2008.txt. This isn’t necessarily recommended, but it’s possible; you never know what you’re going to run into when you’re reading through folders.

If we knew for sure there would be only one dot we could find the file extension by referencing the second array element:

strExtension = arrSplitName(1)

But because there could be more than two elements in the array, we want to make sure we get the last one, which will always be the extension. Therefore, we use the UBound function to return the last element in the array. Because arrays start indexing at 0 we subtract 1 from the results of UBound – after all, we don’t want to index the number of elements in the array, we want to index the last item, which is always the number of elements minus 1.

That logic makes sense, right? The only problem with it is that UBound doesn’t return the number of elements in the array, it returns the index number of the last element in the array. That means we need to remove the – 1 in the equation:

strExtension = arrSplitName(UBound(arrSplitName))

Change "m" to "d" in the Call to DateAdd

The instructions for this event said that we want all files that were created more than 10 days ago. So one of the first things we want to do in the script is find out when 10 days ago was. Thus we put in this line of code:

dtmOld = DateAdd("m", -10, Now)

The problem? This call to DateAdd gets the date 10 months ago, not 10 days ago. How can we tell? Well, the first parameter to DateAdd indicates what portion of the date we’re working with. An m indicates months. If we want to add (or subtract) days, the first parameter needs to be a d:

dtmOld = DateAdd("d", -10, Now)

Fix Typo in End If

This was an easy one; VBScript should have caught this one the minute you tried to run the script. There’s an If statement in the CopyTextFiles subroutine. Every If statement needs to end with an End If. Well, take a look at the End If in the broken script:

Edn If

Oops. Change Edn to End and this will work fine.

Add Sub to End Sub

Here’s another one VBScript should have caught for you. When you declare a subroutine, you specify the end of the subroutine with an End Sub statement. At the end of our CopyTextFiles subroutine we have this:

End

We forgot the Sub. Change this to End Sub and this particular problem is solved.

Change > to <

As we’ve mentioned, and as the event instructions mentioned, this script needs to find all text files with a creation date less than 10 days ago. However, look at our If statement that compares the date the file was created (objFile.DateCreated) to the date 10 days ago (dtmOld):

If strExtension = "txt" and objFile.DateCreated > dtmOld Then

This statement is checking to see if the creation date is greater than 10 days ago, not less than. Change the greater than sign to a less than sign and we’ll get the right set of files:

If strExtension = "txt" and objFile.DateCreated < dtmOld Then

Add Parentheses to the AllFolders Subroutine

In VBScript, any time a function or subroutine is declared, the parameters go inside parentheses. We did this correctly when we defined the CopyTextFiles subroutine:

Sub CopyTextFiles(subFolder)

But we forgot the parentheses when we defined the AllFolders subroutine:

Sub AllFolders Folder

Add parentheses around the parameter and this error goes away:

Sub AllFolders (Folder)

Add "Scripting." to FileSystemObject

Last, but certainly not least, is the line where we retrieve an object reference to the FileSystemObject object:

Set objFSO = CreateObject("FileSystemObject")

Yes, we frequently refer to this as the FileSystemObject, but when you create a reference to it you need to refer to the Scripting.FileSystemObject:

Set objFSO = CreateObject("Scripting.FileSystemObject")

That’s everything. Here’s the completed, working, script:

i = 0

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder("C:\scripts")

dtmOld = DateAdd("d", -10, Now)
 
AllFolders(objFolder)

CopyTextFiles objFolder

Sub AllFolders(Folder)
    For Each Subfolder in Folder.SubFolders

        CopyTextFiles subfolder
        AllFolders Subfolder

    Next
End Sub


Sub CopyTextFiles(subFolder)

    Set colFiles = SubFolder.Files

    For Each objFile in colFiles
        arrSplitName = Split(objFile.Name, ".")
        strExtension = arrSplitName(UBound(arrSplitName))
        If strExtension = "txt" and objFile.DateCreated < dtmOld Then
            objFSO.CopyFile objFile.Path, "C:\old\"
            Wscript.Echo objFile.Name
            i = i + 1
        End If
    Next

End Sub

Wscript.Echo
Wscript.Echo "Total Files: " & i

Top of pageTop of page