Hey, Scripting Guy!Our Favorite Shell Games

The Microsoft Scripting Guys

Download the code for this article: HeyScriptingGuy2008_03.exe (150KB)

According to the old proverb, even the tiniest sparrow cannot fall to earth without the heavens knowing about it. Now, the Scripting Guys aren't suggesting that people go around knocking sparrows out of the sky. (Although if someone wants to take care of those stupid crows who sit on the roof and screech at 7:00 A.M. on a Saturday, well, that's a different story. [The Scripting Editor likes crows, and so we're sure that by "take care of" the Scripting Guy who writes this column means "make sure they stay healthy."-Ed.]) Nevertheless, it is comforting to know that if you do fall, someone cares, and that no matter how small and insignificant you might seem, someone somewhere is watching over you. [Even if it's just an editor.-Ed.]

What's true for sparrows turns out to be true for system administration scripting as well. It's safe to say that, in the scripting world, most of the attention is focused on the big scripting kahunas: Windows PowerShellTM, VBScript, WMI, ADSI, even the FileSystemObject. It's understandable that these technologies get the most hype and the most publicity; after all, they enable you to do a lot of really cool and useful things. But that doesn't mean that these are the only technologies available to scripters. Far from it.

Take the Shell object, for example. While the Shell object might not be as well-known or as widely publicized as Windows PowerShell, it's still important—and still useful.

There's a good chance that, sooner or later, someone will ask you to write a system administration script that uses the Shell object. That leads to an obvious question: what exactly can you do with the Shell object? As it turns out, there are a lot of cool things you can do with the Shell object, some of which (such as managing disk quotas and displaying a progress bar when you copy or move files) have been covered in the Microsoft® Windows® 2000 Scripting Guide (microsoft.com/technet/scriptcenter/guide). In this month's column, we're going to show you a few other nifty little things you can do with the Shell object, things you probably had no idea could be done at all, let alone by using the Shell object.

Changing the Last-Modified Date of a File

Many of you are probably looking at the preceding headline and thinking, "Hey, wait a second. You can't change the last-modified date of a file using a script, at least not using VBScript." To which we can say only one thing: where in the world did you ever get an idea like that?

Oh, right; we Scripting Guys probably have said that in the past, haven't we? Well, as it turns out, we were wrong. Shocking! You can change the last-modified date of a file using VBScript. And all you have to do is use the Shell object.

Note: Don't bother asking how the Scripting Guys could have been wrong about that; after all these years, that should be pretty obvious. A far more interesting question is this: how in the world do we ever manage to get something right?

So you say you want to change the last-modified date of a file, huh? Then just use this script:

Set objShell = _
  CreateObject("Shell.Application")

Set objFolder = _
  objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
  objFolder.ParseName("Dr_Scripto.jpg")

objFolderItem.ModifyDate = _
  "01/01/2008 8:00:00 AM"

As you can see, there's nothing too terribly complicated about this. We start out by creating an instance of the Shell.Application object. (By the way, don't confuse Shell.Application with Wscript.Shell, the Windows Script Host Shell object. The Shell object we're working with today is the Windows Shell object.) After our instance of the Shell object is up and running, we use the Namespace method to bind to the folder C:\Scripts, then use the oddly named ParseName method to bind to a specified file within that folder. In this case, we're binding to a JPEG image with the file name Dr_Scripto.jpg:

Set objFolderItem = _
  objFolder.ParseName("Dr_Scripto.jpg")

And what do we do after we bind to the file itself? That's easy; we simply assign a new date and time to the ModifyDate property:

objFolderItem.ModifyDate = _
  "01/01/2008 8:00:00 AM"

That's all we have to do.

You know, you're right. That is pretty cool, isn't it? Once we run the script, the last-modified date for the file will be set to January 1, 2008, at 8:00 A.M. (If you want, you can open the folder C:\Scripts in Windows Explorer to verify that for yourself.)

Of course, some of you might be thinking, "Cool. I can change the last-modified date of a file. But why in the world would I want to change the last-modified date of a file?" Well, lots of times people use the last-modified date as a sort of version control system; if you have multiple copies of, say, a script floating around, one way to track the official version is to double-check the last-modified date. By checking the last-modified date you can tell whether or not a particular copy of that script is an unmodified version of the original script.

Note: OK, sure, someone can run the script we just showed you and change the last-modified date. But short of code-signing everything you do, someone can always find some way to beat the system. Assuming you have ethical peers that you share scripts with, this approach is a reasonable one.

At some point you might decide that you want to update—and standardize—the last-modified date for all your scripts. How can you do that? Well, assuming all your scripts are in the folder C:\Scripts, you can simply run the following chunk of code:

Set objShell = _
  CreateObject("Shell.Application")

Set objFolder = _
  objShell.NameSpace("C:\Scripts")
Set colItems = objFolder.Items

For Each objItem In colItems
    objItem.ModifyDate  = _
      "01/01/2008 8:00:00 AM"
Next

As you can see, this script starts off in a very similar fashion to the first script we showed you. However, in this case, after we bind to the C:\Scripts folder, we don't use ParseName to bind to an individual file in that folder. Instead, we use this line of code in order to return a collection of all the files found in that folder:

Set colItems = objFolder.Items

Once we have our collection, we then set up a For Each loop to loop through all the items in the collection. Inside the For Each loop, we use this handy line of code to change the value of the ModifyDate property of the first file in the collection to January 1, 2008, at 8:00 A.M:

objItem.ModifyDate  = _
  "01/01/2008 8:00:00 AM"

And then we simply loop around and repeat the process with the next file in the collection. When all is said and done, all the files in the folder C:\Scripts (well, except for any hidden files) will have the exact same last-modified date. See how easy that is? Now, who said you can't change the last-modified date of a file using VBScript?

Oh, right.

Speaking of Being Wrong ...

Back before the dawn of time, when the Scripting Guys first started working with system administration scripting, there was no way to access all the extended file information that gets attached to a file in the NTFS file system. For example, if you right-click on a .wav file and then click Properties, you'll see information similar to what's in Figure 1.

Figure 1 File properties summary

Figure 1** File properties summary **

How do you retrieve values like Bit Rate or Audio sample size using a script? You can't. Well, not unless you use the Shell object, that is. (Or Windows Desktop Search 3.0, assuming you've downloaded and installed that software.) Somewhere along the way the ability to retrieve all the extended property information for a file was added to the Shell object, but the Scripting Guys somehow overlooked that fact. Consequently, we've been telling people, "You can't use a script to determine the bit rate of a .wav file," even though, as the code in Figure 2 demonstrates, you actually can.

Figure 2 Use a script to find the bit rate of a .wav file

Set objShell = CreateObject("Shell.Application")

Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = objFolder.ParseName("J0388563.wav")

For i = 0 to 33
    strHeader = objFolder.GetDetailsOf(objFolder.Items, i)
    strValue = objFolder.GetDetailsOf(objFolderItem, i)
    If strValue <> "" Then
        Wscript.Echo strHeader & vbTab & strValue
    End If
Next

How does this script work? Well, this one also starts out just like the first script we showed you: we create an instance of the Shell.Application object and we bind to the C:\Scripts folder, then we use the ParseName method to bind to the file in question (in this case, J0388563.wav).

At this point, things get a little tricky. To begin with, we set up a For Next loop that runs from 0 to 33; that's because files support as many as 31 different extended properties that have been given index numbers from 0 through 33. (Note that there are a few numbers, such as 27 and 28, that are valid index numbers but don't contain any property values.) In fact, files support the properties shown in Figure 3.

Figure 3 File properties

Index Property
0 Name
1 Size
2 Type
3 Date Modified
4 Date Created
5 Date Accessed
6 Attributes
7 Status
8 Owner
9 Author
10 Title
11 Subject
12 Category
13 Pages
14 Comments
15 Copyright
16 Artist
17 Album Title
18 Year
19 Track Number
20 Genre
21 Duration
22 Bit Rate
23 Protected
24 Camera Model
25 Date Picture Taken
26 Dimensions
29 Episode Name
30 Program Description
32 Audio Sample Size
33 Audio Sample Rate

So what do we do inside that For Next loop? Well, the first thing we do is execute this line of code:

strHeader = _
  objFolder.GetDetailsOf(objFolder.Items, i)

What we're doing here is using the GetDetailsOf method to retrieve the name of property 0 (remember, our loop runs from 0 to 33). We then store that name in a variable named strHeader, and we use this line of code to retrieve the property value of item 0, storing the returned information in a variable named strValue:

strValue = _
  objFolder.GetDetailsOf(objFolderItem, i)

See how that works? Item 0 happens to be the Name property. So the first time through the loop, strHeader will be equal to Name. Meanwhile, the actual name (that is, the value of the Name property) for the first file is J0388563.wav. Therefore, the first time through the loop, strValue will be equal to J0388563.wav. When we put it that way, it doesn't seem so complicated after all, does it?

As you probably know, not all file types support all the extended properties. (For example, a .jpg file isn't going to have an Album Title or Audio Sample Rate.) Therefore, in the next line of code, we check to see whether a returned value is an empty string:

If strValue <> "" Then

If the value is an empty string, then we simply skip back to the top of the loop and repeat the process with the next extended property. If the value isn't an empty string, we echo back the property name and value, like so:

Wscript.Echo strHeader & vbTab & strValue

The net result? Something very similar to this:

Name    j0388563.wav
Size    169 KB
Type    Wave Sound
Date Modified   1/19/2004 8:56 AM
Date Created    3/6/2006 2:02 PM
Date Accessed   12/3/2007 10:41 AM
Attributes      A
Status  Online
Owner   FABRIKAM\kenmyer
Bit Rate        90kbps
Audio Sample Size       4 bit
Audio Sample Rate       11 kHz

Nice.

We're running a little short on time (and space) here, but if you're looking for an even easier way to return the most relevant property values, try the script in Figure 4 instead.

Figure 4 An easy way to return property values

Const colInfoTip = -1

Set objShell = CreateObject("Shell.Application")

Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
  objFolder.ParseName("01. Out On The Weekend (Album Version).wma)")

Wscript.Echo objFolder.GetDetailsOf(objFolderItem, colInfoTip)

What does this script do? Well, if you're in Windows Explorer and you hold the mouse pointer over a file, a tooltip will pop up and give you some useful information about the file, like the information shown in Figure 5. How can you get that tooltip information using a script? That's right. Use the script we just showed you:

Figure 5 Properties shown in a tooltip

Figure 5** Properties shown in a tooltip **

Artist: Neil Young
Album Title: Harvest
Year: 1972
Track Number: 1
Duration: 0:04:35
Type: Windows Media Audio file
Bit Rate: 256kbps
Protected: Yes
Size: 5.31 MB 

That's all the time we have for this month. Well, that and one last note about sparrows. One reason you might want to keep track of tiny sparrows is this: in some parts of the world, it's considered bad luck for a sparrow to enter your house. Why? Because that means that someone who lives in the house is going to die. (In some cases that's true only if the sparrow lands on a piano. But that's not a superstition, that's just weird.) But here's the interesting part: in other parts of the world, having a sparrow enter your house means that someone who lives in that house is going to get married. Talk about being between a rock and a hard place, huh?

Dr. Scripto's Scripting Perplexer

The monthly challenge that tests not only your puzzle-solving skills, but also your scripting skills.

March 2008: Cryptic Script

This month's puzzle is a cryptogram, meaning each letter has been replaced by a different letter. (We've left all symbols and numbers intact.) The same letter will always represent a replaced letter. For example, all occurrences of the letter a might be replaced by the letter b, all occurrences of b would be replaced by z, and so on. Here's a simple example:

tdsjqu dfoufs

decodes to this:

script center

The decoding in this case was done by replacing each letter with the letter of the alphabet that precedes it, so t was replaced by s, d was replaced by c, and so on.

The puzzle is a little more random than that. Decode this script and you'll have a script that opens an instance of Microsoft Excel®, adds a new spreadsheet, assigns four values to the first column in the spreadsheet, and then sorts that column in ascending order.

Have fun!

The Puzzle

kqyjs edfjkzyrlyg = 1
kqyjs edyq = 2
kqyjs edjqtstqmj = 2

jzs quwzekzd = ktzfszquwzks("zekzd.faadlkfslqy")
quwzekzd.iljludz = stcz

jzs quwmqtxuqqx = quwzekzd.mqtxuqqxj.frr
jzs quwmqtxjozzs = quwmqtxuqqx.mqtxjozzsj(1)

quwmqtxjozzs.kzddj(1, 1) = "kfs"
quwmqtxjozzs.kzddj(1, 2) = "rqg"
quwmqtxjozzs.kzddj(1, 3) = "ufs"
quwmqtxjozzs.kzddj(1, 4) = "faz"

jzs quwtfygz = quwzekzd.fkslizkzdd.zysltztqm
quwtfygz.jqts quwtfygz, edfjkzyrlyg, , , , , , edyq, , , edjqtstqmj

ANSWER:

Dr. Scripto's Scripting Perplexer

Answer: Cryptic Script, March 2008

Here's the key to decoding the script:

Actual Letter   a   b   c   d   e   f   g   h   i   j   k
Coded Letter    f   u   k   r   z   b   g   o   l   w   x

Actual Letter   l   m   n   o   p   q   r   s   t   u   v   w   x   y   z
Coded Letter    d   n   y   q   a   h   t   j   s   c   i   m   e   v   p

And here's the actual decoded script:

Const xlAscending = 1
Const xlNo = 2
Const xlSortRows = 2

Set objExcel = CreateObject("Excel.Application")
objExcel.Visible = True

Set objWorkbook = objExcel.Workbooks.Add
Set objWorksheet = objWorkbook.Worksheets(1)

objWorksheet.Cells(1, 1) = "Cat"
objWorksheet.Cells(1, 2) = "Dog"
objWorksheet.Cells(1, 3) = "Bat"
objWorksheet.Cells(1, 4) = "Ape"

Set objRange = objExcel.ActiveCell.EntireRow
objRange.Sort objRange, xlAscending, , , , , , xlNo, , , xlSortRows

The Microsoft Scripting GuysThe Scripting Guys work for—well, are employed by—Microsoft. When not playing/coaching/watching baseball (and various other activities), they run the TechNet Script Center. Check it out at www.scriptingguys.com.

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.