Hey, Scripting Guy! It’s About Time (Oh, and About Dates, Too)
The Microsoft Scripting Guys spend most of their time at the beach...oh, um, no, we mean: The Scripting Guys are hard-working members of the Microsoft Windows Server User Assistance team. To find out more, go to "About The Scripting Guys".
© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.
The Microsoft Scripting Guys
Download the code for this article: ScriptingGuy2006_07.exe (151KB)
You know, the Scripting Guys hardly ever wax philosophical. (We hardly ever wax our cars or our floors, either, but that’s a different story.) When it comes to the nature of time, however, well, in that case we just can’t help ourselves. That’s because time is such an interesting phenomenon. After all, you can call time, cheat time, make time, waste time, steal time, and keep time; heck, you can even kill time. (Disclaimer: no time was harmed in the making of this column.) Time flies, time marches on, and time (not to mention tide) waits for no man. And yet, time is on your side and time heals all wounds, even though there’s no time like the present and definitely no time for sergeants. We could go on, but we simply don’t have the time.
OK, that’s not true. We actually have lots of time. It’s just that the editors of this magazine have certain expectations, one of them being that a scripting column should include information about scripting. Although, considering what they pay us, they should be happy to get any kind of column. Haven’t they ever heard the expression "time is money"?
Unfortunately, there is something you can’t do with time, at least not easily: make any sense of it in a Windows® Management Instrumentation (WMI) script. For example, consider this simple little script, which tells you the date and time that the operating system was installed on a computer (the value of the InstallDate property):So when was the operating system installed on this computer? Why, on the date shown in Figure 1, of course. Ah, yes: 20031121093604.000000–480. What a great time we had back then, eh? Remember when that one guy did that one thing? Precious memories...
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & _ strComputer & "\root\cimv2") Set colOperatingSystems = _ objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem in _ colOperatingSystems Wscript.Echo objOperatingSystem.InstallDate Next
Actually, this is a valid date and time: 9:36 A.M., November 21, 2003. And, no, the date and time isn’t being stored in, say, Klingon format. Instead, WMI stores dates and times in the Universal Time Coordinate (UTC) format. We won’t explain the details of the UTC format here; for more information, read the WMI chapter in the Microsoft® Windows Scripting Guide. Suffice it to say that the InstallDate property returns the date in a valid date-time format. Furthermore, you can use code similar to this function to convert a UTC date-time value to a regular date-time value:
Function WMIDateStringToDate(dtmInstallDate) WMIDateStringToDate = _ CDate(Mid(dtmInstallDate, 5, 2) &_ "/" &_ Mid(dtmInstallDate, 7, 2) &_ "/" &_ Left(dtmInstallDate, 4) &_ " " &_ Mid (dtmInstallDate, 9, 2) &_ ":" &_ Mid(dtmInstallDate, 11, 2) &_ ":" &_ Mid(dtmInstallDate, 13, 2)) End Function
We know what you’re thinking—this isn’t the most straightforward code you’ve ever seen. (Yes, we know what you’re really thinking, but they won’t let us use words like that in this magazine.)
Oh, and it gets worse. Suppose you want to use WMI to query the event logs for all the events that occurred on 12/19/2005. Can you use the date 12/19/2005 in your WMI Query Language (WQL) query? You wish. Instead, you have to convert 12/19/2005 to its UTC equivalent (something on the order of 20051219000000.000000–480). How nice...
We’re sure by this point you’re wondering, "Man, if those WMI guys are so smart, why can’t they come up with a way to do these conversions for us, and without us having to use Mid and Left and all those other crazy VBScript functions?" Well, it turns out that they did. In Windows XP and Windows Server™ 2003 (sorry, Windows 2000 users, but this enhancement isn’t available on operating systems that predate Windows XP), WMI includes a new object—the SWbemDateTime object—that makes it a snap to convert a UTC date-time value to a regular old date-time value. And vice versa.
No, we’re not pulling your leg; it’s true. To prove it, let’s take a look at a sample script that retrieves the date and time at which the operating system was installed and then reports that information back as a real date-time value.
As you can see in Figure 2, this script is similar to the first script we showed you—the script that reported back the InstallDate as a UTC value. In fact, there are only a couple of differences here. To begin with, we create an instance of the WbemScripting.SWbemDateTime object. As you might have guessed, this gives us an instance of the SWbemDateTime object we can work with:
Set objSWbemDateTime = _ CreateObject("WbemScripting.SWbemDateTime")
Set objSWbemDateTime = _ CreateObject("WbemScripting.SWbemDateTime") strComputer = "." Set objWMIService = GetObject( _ "winmgmts:\\" & _ strComputer & _ "\root\cimv2") Set colOperatingSystems = _ objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem _ in colOperatingSystems objSWbemDateTime.Value = _ objOperatingSystem.InstallDate Wscript.Echo _ objSWbemDateTime.GetVarDate(False) Next
The other difference is that we now use two lines of code to retrieve, convert, and report back the install date. First, we assign the UTC date-time value returned by InstallDate to the Value property of the SWbemDateTime object. That’s what we do here:
objSWbemDateTime.Value = _ objOperatingSystem.InstallDate
Why do we have to do this? Well, when we create an SWbemDateTime object we create a blank object—the object’s properties, such as Value, are all null. Here we’re just assigning a UTC value to our object: the value of the InstallDate property.
All we have to do now is call the GetVarDate method. This converts the UTC value to a regular old date-time value (or, more correctly, a variant date-time value). To keep the coding to a minimum, we echo back this value at the same time we convert it:
Alternatively, we could have stashed this value in a variable:
dtmMyDate = objSWbemDateTime.GetVarDate(False)
And what do we get when we run this script? We get the output shown in Figure 3.
You’re right: it is about time, isn’t it? Some of you noticed the False parameter that we passed to the GetVarDate method. As it turns out, GetVarDate can report back the date and time in one of two ways: correcting for the local time zone or without correcting for the offset (difference) between local time and Greenwich Mean Time (technically, a somewhat nebulous time known as "unzoned time"). What does that mean? Well, suppose we set this parameter to True. In that case, we’ll get back the value shown in Figure 4.
Why the difference? This computer happens to be based in Redmond, WA (the center of the universe), and Redmond time is eight hours behind Greenwich Mean Time. (For a more detailed discussion of local time and unzoned time, see the CIM_DATETIME section of the WMI SDK.) To report the value as Redmond time (or whatever your local time happens to be) set the parameter to True.
Unless, of course, you’re running Windows XP Service Pack 1 (SP1) or "plain-vanilla" Windows XP. Setting the parameter to False under these OSs gives you the corrected time. That’s actually the opposite of what it should do; because this parameter corrects for local time, you’d expect to set it to True to get local time. That’s the case in Windows XP SP2 and Windows Server 2003. With these operating systems, this has been "fixed" and you set the parameter to True in order to correct for local time.
That could pose a problem if you have some computers running Windows XP SP1 and others running Windows XP SP2. In that case, you’ll have to add a bit of logic to check which version of Windows you’re running and then set the value of the parameter accordingly. But hey, if it didn’t have some quirkiness, it wouldn’t be scripting, would it?
Now, what about going the other direction? That is, what about taking a variant date and converting it to a UTC value? Figure 5 shows a script that retrieves a collection of all the event log events recorded on or after January 1, 2006 (that is, all events where the TimeWritten property is greater than or equal to 1/1/2006). To do so, it takes a variant date (1/1/2006), converts that date to a UTC date-time value, and then uses that value in the WQL query.
Set objSWbemDateTime = _ CreateObject("WbemScripting.SWbemDateTime") objSWbemDateTime.SetVarDate #1/1/2006#, True strComputer = "." Set objWMIService = GetObject(_ "winmgmts:\\" &_ strComputer &_ "\root\cimv2") Set colEvents = objWMIService.ExecQuery _ ("Select * from Win32_NTLogEvent _ Where TimeWritten >= ‘" & _ objSWbemDateTime.Value & "’") For Each objEvent in colEvents Wscript.Echo objEvent.TimeWritten Next
As you can see, this is even easier than converting a UTC date to a regular date. After creating an instance of the SWbemDateTime object, we use one line of code to set the Value of the object to 1/1/2006:That’s it—we simply call the SetVarDate method, passing the date (enclosed within # characters, which ensures that VBScript treats the value as a date) and the parameter True. Note that in this case—unlike GetVarDate—True really does mean "Yes, convert this date to local time." That means you can use the same value in Windows XP SP1, Windows XP SP2, and Windows Server 2003.
objSWbemDateTime.SetVarDate #1/1/2006#, True
You probably want to convert the time to local time. After all, we’re using this value in a WQL query that retrieves events written to the event log where the TimeWritten for the event is greater than or equal to the zero-hour on 1/1/2006. If we don’t correct the time for local time, we could end up getting events that were written as many as eight hours prior to the zero-hour, which means we could end up with some events that actually occurred on New Year’s Eve (12/31/2005). Needless to say, many of us don’t want to know what happened on New Year’s Eve, if you catch our drift.
If this is confusing, try running the script twice, once with the parameter set to True and once with the parameter set to False. If you compare the results, you’ll see what we’re talking about.
Now that we’ve converted our date to a UTC date, we can use the Value of our SWbemDateTime object in our WQL query, like this:Cool, huh?
Set colEvents = objWMIService.ExecQuery _ ("Select * from Win32_NTLogEvent _ Where TimeWritten >= ‘" & _ objSWbemDateTime.Value & "’")
Now, admittedly, we chose a pretty easy case here—any event that occurred after the year 2005. We could get a bit fancier and retrieve only events that occurred between 2:00 P.M. on January 1st and 6:00 P.M. on January 3rd. But that’s a tad bit more complicated and something we’ll have to address later on; after all, right now we really are out of time.
Before we go, we’d like to remind you of the words of author and activist Louise Hart: "The best thing to spend on your children is your time." Maybe that’s true, but we’re willing to bet there were some awfully disappointed kids at the Hart household last Christmas morning.
A Note from the Scripting Guys
If you’re walking down the street and you see a crowd of people looking up at the sky, do you stop and look? Of course you do. Even if you’re trying not to, you still take a subtle peek. On the one hand, this could be some sort of human behavior study and you’ve just responded as an obedient guinea pig. On the other hand, there could be a UFO hovering overhead and you certainly don’t want to miss the first sight of life from another planet. Or there could be something dangling from a crane, ready to fall on your head if you don’t watch out. Whatever the reason, your curiosity will typically get the better of you and you’ll look.
Take a Look
That brings us to the Script Center. Why are system administrators flocking to the Script Center and looking around? Come find out for yourself. Chances are nothing will fall on your head if you don’t, but you could still eliminate a certain amount of pain from your life if you do.
The Script Center is overflowing (can a Web site overflow?) with resources to help you automate your Windows systems. In addition to thousands of scripts, you’ll find daily Q & A articles (that answer actual user questions), puzzles to improve your troubleshooting skills, and even something called Dr. Scripto’s Fun Zone. (Missing the Fun Zone would be like missing the UFO. OK, not exactly like that, but close.) All of this is meant to educate you and help you become more efficient at your job—and here’s the important part—without putting you to sleep in the process. It’s one of those "you have to see it to believe it" things.
Tell Me Something I Don’t Know
Now you’re waiting for us to throw in some steak knives for just $19.99, aren’t you? Well, that’s not going to happen. (We’re usually not allowed to handle sharp objects.) But here are some new things we can provide:
- Information about the new scripting capabilities of Windows Vista.
- New articles every day and a new puzzle each week.
- New scripts in the Community-Submitted Scripts Center provided by your fellow script writers. (You can even submit your own scripts that you’d like to share.)
And finally, join us in July as we celebrate the 500th episode of Hey, Scripting Guy! For complete details on the celebration (and maybe even some prizes), visit us at microsoft.com/technet/scriptcenter.