Hey, Scripting Guy! Question

Hey, Scripting Guy! We have an in-house application that, unfortunately, crashes a lot; when that happens we have to go through a lengthy procedure to restart the program. Using Windows PowerShell, is there any way I can be notified when this application crashes?

-- OI

SpacerHey, Scripting Guy! AnswerScript Center

Hey, OI. You know, at first we thoughts to ourselves, we thought, “Hey, we ain’t a-gonna answer none of them there questions today.” And we really wuzn’t a-gonna do it, no sir. But now we is. Ain’t that a kick in the head?

Sorry; we got a little carried away there. It’s just that we’re a bit giddy with excitement this morning. Remember that scene in The Wizard of Oz where everyone dances around singing, “Ding-dong the witch is dead”? Well, we feel the same way: this week the Scripting Editor is down in San Diego attending the Microsoft Management Summit (MMS); that gives us at least a temporary reprieve from her red-pencil reign of terror. For years we’ve been waiting for the chance to misspell words; now we can. For years we’ve been waiting for the chance to use bad grammar; now we can. For years we’ve been waiting for the chance to write entire paragraphs – no, make that entire columns – that don’t make a bit a sense. And now we – well, OK, that we have been doing. But at least now we can write meaningless columns without having her interrupt us every two minutes and ask, “What is this supposed to mean?”

Come on, everybody sing: ding-dong the witch is dead ….

Sorry. We’re still a little giddy.

Interestingly enough, another thing we’ve wanted to do for years is show you how, using Windows PowerShell, you can be notified any time a particular process is terminated. For some reason, however, the Scripting Editor would never let us do that, either. But now there’s no way that she can stop us.

Note. Evil? Well, evil is a pretty strong word. But, yes, we’d have to say that she’s evil.

As it turns out, working with event notifications in Windows PowerShell is a bit tricky; that’s because PowerShell’s Get-WMIObject Cmdlet doesn’t support event notification queries. (What’s an event notification query? See this Scripting Guys webcast for more information.) So why doesn’t Get-WMIObject support event notification queries? To be honest, we don’t know: you-know-who would never let us look into that. For now, however, all that matters is that we can’t use good old-fashioned WMI to receive a notification any time a process terminates. Instead, we have to take advantage of a couple other options available to us.

One of those options involves the Get-Process Cmdlet. In the scenario OI described, we don’t need notification any time a process is terminated; we only need notification when a particular process (for our purposes, Calculator) is terminated. Because of that we can create an event notification script with just three lines of code:

$a = get-process calc
$a.waitforexit()
"Calculator has stopped running."

To begin with, we use the Get-Process Cmdlet to bind to the process with the name Calc. (The name, by the way, is the executable file name minus the file extension.) It’s important to note that we’re assuming we have only one process with the name Calc. What if we have multiple instances of Calculator running? Well, in that case this first line of code will work – we’ll get back a collection consisting of all those instances of Calculator – but the script will fail on line 2. But don’t fret: we’ll show you a more generic process monitoring script in a few minutes.

Once we’ve gotten a handle to the Calc process we then call the WaitForExit() method:

$a.waitforexit()

What’s that going to do? That’s going to cause the script to “block;” that means that the script will simply sit there and wait until the process in question has terminated. When that process does terminate we then use the third and final line of code to echo back the fact that Calculator has stopped running.

Now, this script works just fine; try it and see for yourself. However, that doesn’t mean that the script is perfect; for example, each time Calculator stops the script stops as well. That means you’ll need to restart both Calculator and the monitoring script.

And yes, that is the way the Scripting Editor would make you do things. But remember, the Scripting Editor isn’t here; that means we can show you an alternate approach:

$b = 1

do 
    {
        $a = get-process calc
        $a.waitforexit()
        Invoke-Item c:\windows\system32\calc.exe

    } 
while ($b -eq 1)

What are we doing here? We’re setting up a Do loop that runs as long as the variable $b is equal to 1. (And because $b will always be equal to 1 that means that the loop will run forever and ever.) Inside the loop we again bind to the Calc process and call the WaitForExit() method. But notice what we do if and when Calculator terminates. Instead of echoing a message to the screen we use the Invoke-Item Cmdlet to restart the application:

Invoke-Item c:\windows\system32\calc.exe

And once Calculator restarts we loop around and resume monitoring. We don’t know what OI means by “a lengthy procedure” to restart the in-house application, but if it’s possible to do that programmatically then this might be a better way to go. Of course, in addition to restarting the application we can also report the fact that Calculator had to be restarted, maybe by echoing a message to the screen or writing a note to a log file or the event log or, well, whatever. That’s entirely up to you.

Note. Like we said, the preceding script is designed to run forever; the only way to stop it is to terminate that particular instance of Windows PowerShell. It’s possible to modify the script so that it stops any time you, say, press the Q key on the keyboard, but that’s a more involved procedure than we have time for today.

Like the first script we showed you, our fancy restart-the-process script works only if you have one instance of the target process running; if you have multiple instances of Calculator then you’re going to run into trouble. In addition, this script works only with processes; that’s because the Get-Process Cmdlet includes a WaitForExit() method. That’s not necessarily going to be the case for other entities.

With that in mind let’s take a look at a more generic event monitoring script, one that uses the .NET Framework. First we’ll show you the code, then we’ll briefly describe how the script works:

$a = 0

$timespan = New-Object System.TimeSpan(0, 0, 1)
$scope = New-Object System.Management.ManagementScope("\\.\root\cimV2")
$query = New-Object System.Management.WQLEventQuery `
    ("__InstanceDeletionEvent",$timespan, "TargetInstance ISA 'Win32_Process'" )
$watcher = New-Object System.Management.ManagementEventWatcher($scope,$query)

do 
    {
        $b = $watcher.WaitForNextEvent()
        $b.TargetInstance.Name
    } 
while ($a -ne 1)

With this script we’re checking, every second, for deleted processes. How do we know we’re going to check every second? Because of the way we configured our System.Timespan object:

$timespan = New-Object System.TimeSpan(0, 0, 1)

As you can see, we included three parameters – 0, 0, and 1 – when creating the new object. The first 0 represents hours, the second represents minutes, and the 1 represents seconds. What if we wanted to check for deleted processes every 15 minutes and 30 seconds? Then we’d use this line of code:

$timespan = New-Object System.TimeSpan(0, 15, 30)

And how do we know we’re looking for processes that have been deleted? Because that’s what we asked for when we set up our WQLEventQuery:

$query = New-Object System.Management.WQLEventQuery `
    ("__InstanceDeletionEvent",$timespan, "TargetInstance ISA 'Win32_Process'" )

See? We’re looking for instances of the __InstanceDeletionEvent class, but only if those instances happen to be processes (that is, members of the Win32_Process class).

Note. Yes, that’s a cursory explanation, at best, of terms like __InstanceDeletionEvent and TargetInstance. But you can take a look at the aforementioned-webcast for more details.

After creating an instance of the System.Management.ManagementEventWatcher class we then go into another endless loop and wait for processes to be terminated. Each time a process ends we use this line of code to report back the process Name:

$b.TargetInstance.Name

Of course, we could get fancier here and issue an alert only if, say, the terminated process happened to be an instance of Calculator. But we’ll let you do that sort of modification yourself.

Oh, and thanks for asking: as a matter of fact, the Scripting Editor is planning to report back on her trip to MMS. Will that report be as interesting and entertaining as this column? Heavens no. But at least every word will be spelled correctly.

Editor’s Note: The really amazing thing about the Scripting Editor is that she carries around a crystal ball (sometimes referred to as a laptop computer) that allows her to keep an eye on the other Scripting Guys from great distances. We won’t discuss the consequences they’ll suffer for their sad attempt to defy the powers of the Scripting Editor, but rest assured it will be…unpleasant. Not that they’ll ever learn their lesson….

And keep an eye out on the Script Center for the Scripting Editor’s riveting account of Microsoft Management Summit 2007 – or at least her riveting account of the weather in San Diego.