Hey, Scripting Guy! Question

Hey, Scripting Guy! We have this application that is supposed to run all the time. It is constantly giving us headaches and crashing. The applications vendor evidently expects it to crash all the time and has a thread that will restart it three times. After that, it is pretty much down for the count. The problem is that we never know when the application was restarted or how far along we are on the "three strikes and you are out" rule. I was thinking of some kind of monitoring script that would let us know when the process was terminated, and print out the time. It does not need to run all the time; it only needs to be able to count to three (if that would make it easier). Thanks for your help.

- RV

SpacerHey, Scripting Guy! Answer

Hi RV,

It seems that every company I have ever worked with has at least one copy of Crap App, if you follow me. It also seems that Crap App is mission critical. In many cases, the opportunity for an upgrade to Crap App is nil because the company went out of business because of their (you know what's coming) crappy apps.

This week we are talking about WMI eventing. For some VBScript examples of these kinds of scripts, you can refer to the eventing section of the Hey, Scripting Guy! archive. Additional information can be obtained from this Web page. The Microsoft book, Microsoft Windows Scripting with WMI: Self-Paced Learning Edition, has an entire chapter on WMI eventing.

We decided to write a script called MonitorForProcessDeletionAndCount.ps1. (No, we do not get paid by the letter for these script names, but maybe we should talk to our agent about that.) The MonitorForProcessDeletionAndCount.ps1 script displays a message that it is waiting for a process to terminate, when one is detected, and it displays the name of the process and the time when it was detected. It keeps this up for three times by default. But you could easily change it to monitor for a greater number of occurrences. The MonitorForProcessDeletionAndCount.ps1 script is seen here:

Clear-Host
$dteStart = get-date
$total = 3
$monitorInterval = 5
Write-Host "Waiting for a process to be terminated ... 
You will be notified within $monitorInterval seconds of process termination
This script will monitor for $total occurences
        start time was $dteStart"
	
$strQuery = "select * from __instanceDeletionEvent within $monitorInterval 
          where targetInstance isa 'win32_Process'"
$objEventwatcher = New-Object management.managementEventWatcher $strQuery

for ($i = 1; $i -le $total ; $i++)
{
 $objEvent = $objEventwatcher.waitForNextEvent() 
 "$($objEvent.targetInstance.name) was terminated at $(Get-Date)"
}

The first thing we do is clear the screen of our Windows PowerShell console. We use the Clear-Host function to do this. If you have ever wondered why tab expansion does not expand the name of Clear-Host, it is because it is a function and not a cmdlet. The cool thing about a function is you can see what it is doing by using the Get-Content cmdlet and pointing to the function drive. This is illustrated here, where we display the content of the Clear-Host function.

PS C:\AutoDoc> Get-Content Function:\Clear-Host
$spaceType = [System.Management.Automation.Host.BufferCell]; $space = [System.Activator]::CreateInstance($spaceType); $
space.Character = ' '; $space.ForegroundColor = $host.ui.rawui.ForegroundColor; $space.BackgroundColor = $host.ui.rawui
.BackgroundColor; $rectType = [System.Management.Automation.Host.Rectangle]; $rect = [System.Activator]::CreateInstance
($rectType); $rect.Top = $rect.Bottom = $rect.Right = $rect.Left = -1; $Host.UI.RawUI.SetBufferContents($rect, $space);
 $coordType = [System.Management.Automation.Host.Coordinates]; $origin = [System.Activator]::CreateInstance($coordType)
; $Host.UI.RawUI.CursorPosition = $origin; 

To use the Clear-Host function, you just type the name, or you can even use the cls alias for the Clear-Host function. However, as a best practice, I never use an alias, even one as innocent as cls, in a script because you are never assured that it will exist. The function call is seen here:

Clear-Host

Next, we use the Get-Date cmdlet to retrieve the date and time, and we store it in the $dteStart variable, as shown here:

$dteStart = get-date

When we have stored the date in a variable, we next need to initialize a couple of variables. The $total variable is used to control how many loops we will make. It is inside the loops that we will detect a process that is terminated. So as a matter of practice, the $total variable is used to determine how many process termination events will be detected. The $monitorInterval variable is used to control how long WMI will wait between polling cycles. We have it set to five seconds. In real life, you should never set this value below 30 seconds; better yet, set it to several minutes. This is because of the processor load that is generated each time WMI triggers a polling cycle. You should test completely to determine if the monitoring load will topple your server. The two lines of code that create and initialize the variables are seen here.

$total = 3
$monitorInterval = 5

Now we need to print out a message to the users to let them know what is going on with the script. We use the Write-Host cmdlet to print out this confirmation message. The double quotation marks are expanding quotation marks, which means that variables placed inside them will be expanded to display the value contained in the variable and not print out the variable name itself. If we used literal quotation marks (single quotes), the variable names themselves would be displayed instead of printing out the values. Here is the Write-Host command:

Write-Host "Waiting for a process to be terminated ... 
You will be notified within $monitorInterval seconds of process termination
This script will monitor for $total occurances
        start time was $dteStart"

Next we create our event query. The WMI class we are querying is the __instanceDeletionEvent class. This class allows us to determine when something goes away on our system. The targetInstance of the __instanceDeletionEvent class is a win32_process. So this means when a process goes away, we will receive an event. We could monitor for an __instanceDeletionEvent that involved other WMI classes using this same syntax. The query is shown here:

$strQuery = "Select * from __instanceDeletionEvent within $monitorInterval 
          where targetInstance isa 'win32_Process'"

When we have created our event query, we can use the New-Object cmdlet to create a new ManagementEventWatcher. To do this, we specify the full name to the class and feed it the event query that was stored in the $strQuery variable. We use the $objEventWatcher variable to hold the returned instance of the System.Management.ManagentEventWatcher class. This is seen here:

$objEventwatcher = New-Object system.management.managementEventWatcher $strQuery

Now we use a for loop to govern how long the script will run looking for events. The for statement is composed of three parts. The first part determines where we will start counting. The second part is used to tell us how far the loop will count, and the last position is used to increment the $i counter variable. In the example shown here, we initialize the $i variable inside the for loop. This is actually a best practice when using these types of limited scope, throw-away varivariables:

for ($i = 1; $i -le $total ; $i++)
{

Inside the for loop, we wait for the next event to arrive. To do this, we use the WaitForNextEvent method from the event watcher object. When the event arrives, we store the returned management base object in the $objEvent variable. We then print out the name property and the time the event arrived. The targetinstance property stores a copy of the win32_process class. We could therefore gain access to any of the properties from that class if we wanted to do so. In our code, shown here, we do not use the Write-Host cmdlet. We use expanding strings to print directly to the console. When we put the object in the quotation marks, it will become unraveled and will display all its properties on the line. To prevent that behavior, we use a Windows PowerShell subexpression. This causes the expression to be evaluated, and the results to be returned to the string. This is seen here:

 
$objEvent = $objEventwatcher.waitForNextEvent() 
 "$($objEvent.targetInstance.name) was terminated at $(Get-Date)"
}

After the script has looped through the for loop the specified number of times, the script will exit.

The output from the script is seen here:

Image of the output of the script

 

RV, that is it for process monitoring. Hopefully, it will allow you to get your arms wrapped around your version of Crap App, at least until you can get it replaced or upgraded. Come back and see us tomorrow when we will continue exploring event monitoring scripts. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys