Hey, Scripting Guy! Question

Hey, Scripting Guy! have a folder that has a whole bunch of files in it. Many of those files have a file name that includes an incorrect date; for example, the file might be named FGA Site Visit 2-14-07 204.jpg when it should really be named FGA Site Visit 2-14-08 204.jpg. (That is, it suggests the visit occurred in 2007 rather than 2008.) How can I replace these incorrect dates with the correct dates?

-- SM

SpacerHey, Scripting Guy! AnswerScript Center

Hey, SM. If you’re a regular reader of Hey, Scripting Guy! – you know, you’re right: that is a silly question, isn’t it? After all, who isn’t a regular reader of Hey, Scripting Guy!? To tell you the truth, we can’t think of anyone. Fidel Castro? No, he writes in once or twice a month asking how to manage his DHCP server using WMI.

And once or twice a month we tell him that there is no WMI class for managing DHCP Server. Fidel, please, stop asking about DHCP Server!

At any rate, regular readers of this column know that Hey, Scripting Guy! typically starts off with boring personal anecdotes, meanders on through arcane references to things no one else in the world cares about, touches down once or twice on lame attempts at humor and then finally gets around to answering the question. Well, we hate to disappoint everyone, but there won’t be any idle chitchat in today’s column; at the moment, the Scripting Guy who writes this column has about 950 million things to do for the 2008 Winter Scripting Games, and he keeps falling farther and farther behind. So no fooling around today; we’re just going to answer the question and then get on with it.

Scripting Guys trivia. The huge backlog of Scripting Games-related tasks means that, for the first time ever, the Scripting Guy who writes this column has a bigger to-do list at work than he has at home. And he’s had 13 years to build up that to-do list at home.

Oh, right: we did say something about just answering the question and then getting on with it, didn’t we? Well, OK, if that’s what we said we were going to do then that’s what we’re going to do:

Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Global = True   
objRegEx.Pattern = "(\d{1,2})-(\d{1,2})-07"
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFileList = objWMIService.ExecQuery _
    ("ASSOCIATORS OF {Win32_Directory.Name='C:\Temp'} Where " _
        & "ResultClass = CIM_DataFile")
For Each objFile In colFileList
    strOldName = objFile.Name
    strNewName = objRegEx.Replace(strOldName,"$1-$2-08")
    If strNewName <> strOldName Then 
        errResult = objFile.Rename(strNewName)
    End If
Next

Before we launch into the details of how this script works we should note that we decided to use a regular expression to help us rename these misnamed files. At first glance that might seem like overkill; after all, couldn’t we just search each name for the value 07 and replace it with the value 08? Well, maybe. But, then again, maybe not. For example, suppose our folder (C:\Temp) contains the following set of files:

FGA site visit 1-14-07 204 .vbs
FGA site visit 2-14-07 205.txt
FGA site visit 2-20-07 206.txt
07 DebugMe.ps1
DebugMe-07.pl

As you can see, only the first three files actually contain one of our improper date values; the other two files contain the string 07, but not as a date. That means that we don’t want to replace the 07 in DeBugMe-07.pl; that file is supposed to be named DebugMe-07.pl, not DeBugMe-08.pl. And yet that same file would be renamed if we did a blanket search and replace for all instances of 07.

Using a regular expression helps us guard against that. With a regular expression we can – and will – search for one or two numbers followed by a hyphen, followed by one or two numbers and another hyphen, followed – at long last – by the value 07. That’s not an absolutely foolproof approach; for example, it would change the value of a file named Bob24-99-07.txt even though 24-99-07 isn’t a date, either.

Well, maybe in the Mayan calendar or something. But not the Gregorian calendar.

Note. Was the Gregorian calendar named after Scripting Guy Greg Stemp? Oddly enough, it was actually named after Scripting Guy Jean Ross. They just misspelled Jean.

At any rate, our regular expression isn’t foolproof. But it’s still much safer than simply searching for and replacing any and all instances of 07.

Note. Yes, in theory you could create a regular expression that would be better at distinguishing dates from non-dates. But creating a regular expression like that goes way beyond what we can do in today’s column.

So how do we use this regular expression in our script? Well, we start out by creating an instance of the VBScript.RegExp object, the object that enables us to use regular expressions in the first place. Once we have that object in hand we then assign values to two properties of this object.

The Global property determines whether the script searches for all instances of the target string or just the first instance. With this script it probably doesn’t matter; however, far more often than not you will want to find all instances of the target string. Consequently, we set the value here to True.

The Pattern represents the target text, the value we are searching for. In this case we start off by looking for at least 1, but no more than 2, digits; that’s what this construction is for:

(\d{1,2})

Following the digits comes a hyphen (-) followed by 1 or 2 additional digits and yet another hyphen:

(\d{1,2})-(\d{1,2})-

And then, following that, we have the hardcoded value 07:

(\d{1,2})-(\d{1,2})-07

Note. If this syntax is making your head spin, take a look at our TechNet Magazine article which introduces the use of regular expressions.

After we configure our regular expressions object we next connect to the WMI service on the local computer. (And yes, you could run this same script against a remote computer; just assign the name of that computer to the variable strComputer.) Once the connection is made we use this crazy-looking line of code to retrieve a collection of all the files in the folder C:\Temp:

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

At this point we’re ready to start renaming files. To do that, we first set up a For Each loop to walk through all the files in the collection. Inside that loop, the first thing we do is retrieve the value of the file’s Name property (the WMI equivalent of the file path), assigning that value to a variable named strOldName:

strOldName = objFile.Name

Now it’s time to do a little search-and-replace using our regular expression. That’s what this line of code is for:

strNewName = objRegEx.Replace(strOldName,"$1-$2-08")

Yes, this looks really crazy, but it will search the file path for the target text (two digits, a hyphen, two digits, a hyphen, and the value 07). More important, it will replace the value 07 with the value 08, all the while leaving the month and day in place. That’s what the $1 and the $2 are for.

We’re glad you asked that question. $1 and $2, and $3 are examples of a regular expression “back reference.” A back reference is nothing more than a portion of the found text that can be saved and then reused. In this particular script we’re looking for two “sub-matches”:

A set of 2 digits.

A set of 2 more digits.

Each of these sub-matches is automatically assigned a back reference: the first sub-match is $1; the second is $2; and so on, all the way through $9. In other words, suppose we have a date of 1-14-07. In that case, our regular expression would automatically assigned the following back references:

Date Part

Back Reference

1

$1

14

$2

In our replacement string, we simply use this back references to ensure that the correct month and day are reused. Our replacement text simply says this: Take the first back reference ($1) and follow it with a hyphen. After that, insert the second back reference ($2) followed by another hyphen. Finally, tack on value 08.

What will all that give us? A file path similar to this:

FGA site visit 1-14-08 204 .vbs

After we run the regular expression we then check to see if the new file path is the same as the old file path:

If strNewName <> strOldName Then

What if the two file paths are the same? Well, that simply means that no date value could be found in the file name; that also means that there’s no reason to rename this file. With that in mind we pop back to the top of our For Each loop and try again with the next file in the collection.

As you might have guessed, if the paths are different that means that the file needs to be renamed. That’s something we can do using this one line of code and the Rename method:

errResult = objFile.Rename(strNewName)

And then it’s back to the top of the loop, where we repeat the process with the next file in the collection.

When all is said and done our folder should now contain the following files:

FGA site visit 1-14-08 204 .vbs
FGA site visit 2-14-08 205.txt
FGA site visit 2-20-08 206.txt
07 DebugMe.ps1
DebugMe-07.pl

Nice.

That should do it, SM; if it doesn’t let us know. (Although if you could wait a week or two, until the Scripting Games are over, that would really help.) As for all you Scripting Games competitors who keep writing in asking why your scores haven’t been updated, well, we’re working on it. We hope to be sort of caught up by Monday, but we can’t make any promises. Just be patient.

Which reminds us of a long, pointless, and not-all-that-amusing story, the very thing that the Scripting Guy who writes this column specializes in. As it turns out, the other day the Scripting Son was looking for his baseball glove, and he – well, OK, good point: maybe we should try getting caught up on our Scripting Games duties and then tell our long and pointless story. Once we get caught up just remind us about the story, OK? Thanks.