Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I log the start time and the end time of the screensaver?

-- JS

SpacerHey, Scripting Guy! AnswerScript Center

Hey, JS. You know, one of the Scripting Guys (hey, who said “It must be Greg”?) is old enough to remember the days when screensavers first came out. Back then a script like this would have been meaningless: after all, once a screensaver started everyone was so fascinated by it they never wanted it to end. In fact, one of the first things this Scripting Guy had to do as a computer support person was to make little shortcuts on everyone’s desk so that they could launch their Flying Toasters any time they wanted.

People were so easy to amuse back then.

Ah, but there’s no point living in the past, right? In today’s modern world you obviously have a need not only to stop a screensaver, but to take note of when it stopped. With that in mind, let’s take a look at a WMI event monitoring script that will keep track of each time a screen saver starts and stops:

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

Set objEventSource = objWMIService.ExecNotificationQuery _
    ("SELECT * FROM __InstanceOperationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process'")

Do While True
    Set objEventObject = objEventSource.NextEvent()
        If Right(objEventObject.TargetInstance.Name, 4) = ".scr" Then
        Select Case objEventObject.Path_.Class
            Case "__InstanceCreationEvent"
                Wscript.Echo "Screensaver " & objEventObject.TargetInstance.Name & _
                    " started: " & Now
            Case "__InstanceDeletionEvent"
                Wscript.Echo "Screensaver " & objEventObject.TargetInstance.Name & _
                    " ended: " & Now
        End Select
    End If
Loop

Yes, it does look a little complicated, doesn’t it? But don’t panic: by design WMI event scripts always look a little complicated. Fortunately, the scripts only look complicated; as you’ll see they’re really not all that hard to understand.

Note. OK, we’d better qualify that last statement: they’re not all that hard to understand as long as you’ve been exposed to the basic ideas behind WMI eventing. If that isn’t the case, then you might want to take time out and watch this Scripting Week 2 webcast. The webcast will give you all the background information you need to make sense of today’s column.

Good point; probably nothing will help you make sense of one of our columns. But at least it’ll help you make sense of the script code.

This particular script begins in the time-honored fashion of connecting to the WMI service on the local computer. It’s right about here that we usually execute a WMI query to return information. As it turns out, we’re going to do that in this script, too; it’s just that our query is going to look a little different:

Set objEventSource = objWMIService.ExecNotificationQuery _
    ("SELECT * FROM __InstanceOperationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process'")

Needless to say, this isn’t the type of WMI query you’re used to writing; for one thing, we’re calling the ExecNotificationQuery method rather than ExecQuery. (Why? Because ExecNotificationQuery is the method you have to use in order to monitor WMI events.) We won’t explain this query in minute detail today, but suffice to say that we’re asking WMI to notify us any time a WMI event occurs (something is created, something is deleted, something is modified). There’s just one catch: we want to be notified only if the TargetInstance (the item being created, deleted, or modified) is an instance of the Win32_Process class.

Note. OK, technically there’s a second catch: we’re only checking for new events every 5 seconds. If the screensaver starts and then ends 3 seconds later, we’ll probably never be notified.

In other words, suppose a new file is created. Is a new file an instance of the Win32_Process class? Nope; it’s an instance of the CIM_DataFile class. Therefore, we don’t want to be notified. Suppose a service gets modified. Do we want notification? No; a service is an instance of the Win32_Service class. OK then, suppose a new process - like a screensaver - starts up. Do we want to be notified? You bet: after all, a new process is an instance of the Win32_Process class. We want to be notified any time a process is created, deleted, or modified.

But you already figured that out, didn’t you?

In order to get these notifications we set up a Do loop that runs while True is equal to True:

Do While True

That’s kind of weird syntax, but it’s a syntax that enables the script to continue to run - and continue to monitor the creation, deletion, and modification of processes - until we either terminate the script or restart the computer. If we didn’t have a loop like this, the script would notify us that a screensaver had started, but then the script would end. Consequently, we’d never be notified when the screensaver ended.

The first thing we do inside the loop is execute this line of code:

Set objEventObject = objEventSource.NextEvent()

What we’re doing here is telling the script to wait until the next event of interest occurs; in other words, the script will sit on this line of code until a process is created, deleted, or modified. Suppose our processes never change, suppose we never create, delete, or modify a process. Well, in that case the script will simply sit here forever, waiting patiently. Just in case.

Now, we know what you’re thinking. You’re thinking: “Hey, wait a second. We only care about screensavers. Microsoft Word runs in a process. If we start Microsoft Word, thus creating a new instance of the Winword.exe process, won’t that trigger a notification?”

You’re right: it will. And that’s what our next line of code is for. Starting up Word (or any executable file, for that matter) will, indeed, issue a notification. But we can account for that by using this line of code:

If Right(objEventObject.TargetInstance.Name, 4) = ".scr" Then

Here we’re using the Right function to check the Name of the process that triggered the notification. If the rightmost four characters in the name are equal to .scr, then we’re assuming that we’re dealing with a screensaver; that’s because screensavers have names like Marquee.scr. If the last four characters in the name aren’t .scr then we simply loop around and wait for the next event to occur.

So what if the last four characters are .scr? Well, in that case we’re interested in two possibilities: a screensaver started or a screensaver ended. (We don’t care whether or not someone modifies the properties of a screensaver.) To handle these two possibilities we set up a Select Case block that examines the Class of the event instance:

Select Case objEventObject.Path_.Class

If the Class is equal to __InstanceCreationEvent, that means a new process (i.e., a new screensaver) has been created. In our first Case statement we check to see if the Class is equal to __InstanceCreationEvent. If it is, we echo the fact that a particular screensaver (represented by the process name) started at a particular time (using the VBScript function Now):

Case "__InstanceCreationEvent"
    Wscript.Echo "Screensaver " & objEventObject.TargetInstance.Name & " started: " & Now

That makes sense, doesn’t it? Now, suppose the screensaver has ended, something which would cause the screensaver process to be deleted. To handle that possibility, we check for any new instances of the __InstanceDeletionEvent class. If our event happens to belong to that class (meaning a screensaver process has been deleted) we echo the fact that a given screensaver stopped at a given time:

Case "__InstanceDeletionEvent"
    Wscript.Echo "Screensaver " & objEventObject.TargetInstance.Name & " ended: " & Now

And there you have it. When you run the script you’ll get back information similar to this:

Screensaver Script Center.scr started: 2/9/2006 9:11:07 AM
Screensaver Script Center.scr ended: 2/9/2006 9:11:17 AM

Note. What the heck is Script Center.scr? Download it and find out.

Two things we should add. First, this script is best run in a command window under Cscript; that means that, to start the thing, you might want to open a command window and type something like this (depending on what you name the script, of course):

cscript screensaver_monitor.vbs

Second, and as we pointed out, this script is designed to run forever. On the other hand, nothing lasts forever, does it? If you want to stop monitoring all you have to do is press Ctrl+C, close the command window, or terminate the CScript.exe process. Remember, the Scripting Guys would never leave you trapped inside an endless loop with no way out. (What do you know: that’s a pretty good description of our jobs right there.)