Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I get the uptime of a service?

-- JC

SpacerHey, Scripting Guy! AnswerScript Center

Hey, JC. You know, if you could have seen the Scripting Guy who writes this column this morning you would have reconsidered asking him a question about uptime. Although he woke up in plenty of time, even turning off the alarm before it was due to sound, he had a … little … trouble actually getting up out of bed. Five minutes past the appointed hour and he was still lying there; ten minutes past the appointed hour and he was still lying there – if he hadn’t had to go to the bathroom he might still be in bed. And while he eventually did get out of bed, whether or not he actually woke up is debatable.

Of course, with this Scripting Guy it’s almost-always debatable whether he’s awake or asleep anyway. For example, last year the Scripting Son had a high school baseball game out of town. At the start of the game the Scripting Guy who writes this column was the only fan in the stands. A short time later another fan showed up and somewhat hesitantly asked the Scripting Guy if he knew who was winning. “It’s 2-to-2 in the top of the second. Brandon hit the ball hard his first time out, but the shortstop made a nice play and threw him out at first. However, he did get an RBI by knocking Barrett in.”

The other fan was taken aback. “Oh, I didn’t know you knew anything about baseball,” she said. “You’re always so quiet, even when we score; I assumed you didn’t know anything about the game. To tell you the truth, we always wondered why you even came to the games.”

That’s OK; he’s used to it. Besides, you have no idea how many times a hearse has come to haul this Scripting Guy away, despite his insistence that he was still alive. (Editor’s Note: What he doesn’t know is that the hearse is always sent by the other Scripting Guys.)

But enough with all that; let’s get to work here. The Scripting Guys have always bragged that they could write system administration scripts in their sleep. We’re about to put that notion to the test.

To begin with, let’s take a look at a script that lets you know the date and time that a service first started. As you’ll soon see, there’s a bit of a problem with this script: it doesn’t report the service start time in user-friendly fashion. But that’s OK; after we explain the basic concept behind determining the start time for a service we’ll show you a much friendlier version.

Here’s the basic script for determining the date and time a service first started:

strComputer = "."

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

Set colServices = objWMIService.ExecQuery _
    ("Select * From Win32_Service Where Name = 'Spooler'")
 
For Each objService in colServices
    intProcessID = objService.ProcessID

    Set colProcesses = objWMIService.ExecQuery _
        ("Select * From Win32_Process Where ProcessID = " & intProcessID)

    For Each objProcess in colProcesses
        Wscript.Echo objProcess.CreationDate
    Next
Next

As you can see, we start out by connecting to the WMI service on the local computer, although – that’s right, you can run this script against a remote machine. (Hmmm, you don’t think that, after two-and-half years, we’re beginning to repeat ourselves in this column, do you?) Because we’re interested in the uptime for just one service (in our example, the Print Spooler service), we then issue this query to return a collection of all the services that have the name Spooler (and, yes, because service names must be unique there will be only one such service):

Set colServices = objWMIService.ExecQuery _
    ("Select * From Win32_Service Where Name = 'Spooler'")

Now, ideally, we’d be done at this point: we’d simply report back the date and time that the service started. That would be nice, but, for better or worse, the Win32_Service class doesn’t include a property that tells us when the service started. So does that mean that all is lost? Yes, yes it does.

Ha-ha; sometimes we crack ourselves up. No, in the wonderful world of scripting nothing is ever lost. While it’s true that the Win32_Service class doesn’t have a property that tells us when a service was started, it does have a property that tells us the process ID (PID) of the process in which the service is running. Is that useful? You bet it: we can take that PID and use it to retrieve process information from the Win32_Process class. Is that useful? Of course it is: after all, the Win32_Process class just happens to have a property (CreationDate) that tells us when the process started.

Note. OK, admittedly, this is not 100% foolproof. Why not? Because it’s not unusual for services to run in shared processes; a single process might host multiple services, some of which might be running and some of which might not be running. However, this is the best we can do short of searching through the System event log and looking for service-related events involving, in this case, the Print Spooler service. If you’re interested in trying that, search the System event log for events with an EventCode of 7036, which tracks changes in service status.

After we get back our collection of services we use this line of code to grab the service PID and store it in a variable named intProcessID:

intProcessID = objService.ProcessID

Once we’ve done that we can then retrieve a collection of all the processes that have that particular PID (again, there will be only one item in the resulting collection):

Set colProcesses = objWMIService.ExecQuery _
    ("Select * From Win32_Process Where ProcessID = " & intProcessID)

And then we set up a For Each loop to loop through the one-item collection. Inside that loop all we do is echo back the value of the CreationDate property:

Wscript.Echo objProcess.CreationDate

In turn that gives us output that looks something like this:

20070227074100.796875-480

Yuck. That’s what we were talking about when we said that the information reported back by our first script wasn’t very user-friendly. This isn’t really the fault of the script; the problem is that WMI always reports dates and times as UTC (Universal Time Coordinate) values. Is it possible to convert a UTC value to something a little easier to understand? You bet it is:

strComputer = "."

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

Set colServices = objWMIService.ExecQuery _
    ("Select * From Win32_Service Where Name = 'Spooler'")
 
For Each objService in colServices
    intProcessID = objService.ProcessID

    Set colProcesses = objWMIService.ExecQuery _
        ("Select * From Win32_Process Where ProcessID = " & intProcessID)

    For Each objProcess in colProcesses
        dtmStartTime = objProcess.CreationDate

        dtmConvertedDate = CDate(Mid(dtmStartTime, 5, 2) & "/" & _
            Mid(dtmStartTime, 7, 2) & "/" & Left(dtmStartTime, 4) _
                & " " & Mid (dtmStartTime, 9, 2) & ":" & _
                    Mid(dtmStartTime, 11, 2) & ":" & Mid(dtmStartTime,13, 2))

        Wscript.Echo DateDiff("n", dtmConvertedDate, Now)
    Next
Next

We’ve done two things with this modified script. First, we’ve used this chunk of code to take the value of the CreationDate property and convert it to a “real” date-time value:

dtmStartTime = objProcess.CreationDate

dtmConvertedDate = CDate(Mid(dtmStartTime, 5, 2) & "/" & _
    Mid(dtmStartTime, 7, 2) & "/" & Left(dtmStartTime, 4) _
        & " " & Mid (dtmStartTime, 9, 2) & ":" & _
            Mid(dtmStartTime, 11, 2) & ":" & Mid(dtmStartTime,13, 2))

We won’t discuss this code in any detail today; if you’d like to know more about converting a UTC value to a standard date-time value take a look at this portion of the Microsoft Windows 2000 Scripting Guide. Once we do convert the UTC value to a plain old date-time value we can then use the DateDiff function to report, in this case, the number of minutes that the process has been running:

Wscript.Echo DateDiff("n", dtmConvertedDate, Now)

As you can see, we pass DateDiff three parameters:

“n” indicating the time interval to report. Here we’re reporting the uptime in minutes; for other time intervals (seconds, hour, days, etc.) see the Scripting Guide.

dtmConvertedDate, the date-time value we derived from the CreationDate property.

Now, the current date and time.

This modified script reports the amount of time, in minutes, that the process has been running:

225

Like we said, it’s not the perfect solution, but it’s still a useful script to have in your arsenal. And it does work exceedingly well for services that run in their own process; that’s because stopping a service also terminates the process. And how do you know which services run in their own process? Why, you use this script, of course:

strComputer = "."

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

Set colServices = objWMIService.ExecQuery _
    ("Select * From Win32_Service Where ServiceType = 'Own process'")
 
For Each objService in colServices
    Wscript.Echo objService.DisplayName
Next

There you have it, JC. As for the Scripting Guy who writes this column, he’s ready to call it a day. Is he all worn out after the fun and excitement of the 2007 Winter Scripting Games? No, not really; he’s just lazy. But, in his defense, if he wasn’t lazy then he really wouldn’t be a Scripting Guy, would he?