Although WMI is great for
gathering information, it can also be a very powerful monitoring and
debugging tool. In this section we will use WMI to monitor the processes
on our system and then use WMI to shut down an application that we
define as behaving poorly. Because we don’t want to negatively affect
our system at the moment, we will simulate this sitation by using
NotePad.exe.
Updating the Configuration File
We need to be able to store the process or processes that we want to monitor for. Listing 1 shows the updated configuration file with the process element added.
Listing 1. Process monitoring elements.
<Process>Notepad.exe</Process>
|
We can implement the process element in several
ways, depending on the complexity that we want to create. You can choose
from the following options:
Create a master <Processes> element with subelements called <Process>. You’ll need to modify the <ThreadFunc> method of your Tutorials class so that it can parse all the processes that have been entered. Then you’ll have to update the WMIWorkerOptions class to store an array of process names.
Create
a single element (as I’ve done in the previous example). I called my
element process, and then placed the specific process name that I want
to monitor.
Create a single element but place a delimited list of processes to monitor.
Any of these options will work, though some
require more work than others. For demonstration purposes, I have taken
what I consider the simplest approach and created a single element with a
single process name, Notepad.
WMI Win32_Process Usage
In this section we will use the Win32_Process class (shown in Listing 2)
from WMI to monitor for any application named Notepad.exe. When we find
an instance of Notepad, we will use WMI to shut it down.
Listing 2. Win32_Process WMI class implemented in the .NET Framework in C#.
class Win32_Process : CIM_Process
{
string Caption;
string CommandLine;
string CreationClassName;
datetime CreationDate;
string CSCreationClassName;
string CSName;
string Description;
string ExecutablePath;
uint16 ExecutionState;
string Handle;
uint32 HandleCount;
datetime InstallDate;
uint64 KernelModeTime;
uint32 MaximumWorkingSetSize;
uint32 MinimumWorkingSetSize;
string Name;
string OSCreationClassName;
string OSName;
uint64 OtherOperationCount;
uint64 OtherTransferCount;
uint32 PageFaults;
uint32 PageFileUsage;
uint32 ParentProcessId;
uint32 PeakPageFileUsage;
uint64 PeakVirtualSize;
uint32 PeakWorkingSetSize;
uint32 Priority;
uint64 PrivatePageCount;
uint32 ProcessId;
uint32 QuotaNonPagedPoolUsage;
uint32 QuotaPagedPoolUsage;
uint32 QuotaPeakNonPagedPoolUsage;
uint32 QuotaPeakPagedPoolUsage;
uint64 ReadOperationCount;
uint64 ReadTransferCount;
uint32 SessionId;
string Status;
datetime TerminationDate;
uint32 ThreadCount;
uint64 UserModeTime;
uint64 VirtualSize;
string WindowsVersion;
uint64 WorkingSetSize;
uint64 WriteOperationCount;
uint64 WriteTransferCount;
}
|
We
will not use all of the class properties available to us. System
administrators can use many things to monitor servers based on process
performance: threads, handles, memory, virtual memory, CPU usage, and
more. A well-written service can monitor a nearly unlimited number of
processes per computer while tracking usage over time so that you can
determine whether an application is causing a problem.
Although we will gather several pieces of
information, we will only use the Name property to determine whether we
should shut down the application. This demonstration is not intended to
teach you how to design a monitor, but instead to give you an idea of
what you can do within a service. When we find an application that
matches our <Processes> element, we will shut down the application. We need to make several changes to support this functionality.
Updating the WMIWorkerOptions Class
We need to update the WMIWorkerOptions to store the new process element value that we created. Listing 3 shows the code required to store this information.
Listing 3. WMIWorkerOptions class process update.
Private m_Process As String
Public Property Process() As String
Get
Return m_Process
End Get
Set(ByVal value As String)
m_Process = value
End Set
End Property
|
I added a Process property that we will use to determine the process that we want to monitor for.
Updating the <Tutorials.ThreadFunc> Method
We need to read in the new value from our configuration.xml file and use our WMIWorkerOptions class to store it. We will update the <ThreadFunc> method, as shown in Listing 4.
Listing 4. <ThreadFunc> method update to read the process element.
Do
Try
children = tmpOptions.SelectSingleNode("Query")
WWOptions.Query = children.Value
children = tmpOptions.SelectSingleNode("Server")
WWOptions.Server = children.Value
children = tmpOptions.SelectSingleNode("WMIRoot")
WWOptions.WMIRoot = children.Value
children = tmpOptions.SelectSingleNode("Process")
WWOptions.Process = children.Value
Dim tmpWW As New WMI(m_ThreadAction, WWOptions)
m_WorkerThreads.Add(tmpWW)
tmpWW.Start()
Catch ex As Exception
WriteLogEvent(ex.ToString(), CONFIG_READ_ERROR, EventLogEntryType.Error,
My.Resources.Source)
End Try
Loop While (tmpOptions.MoveToNext)
|
The bolded code in Listing 4
will now use the new process property to store the name of the
process—in our case, Notepad.exe—that we want to monitor for. Now that
we have our process name we need to update our worker threads to query
the processes and then shut down any Notepad.exe instances.
Updating the <Query> Configuration Value
Aside from adding in the appropriate process element, we also need to update our <Query> element to reflect the new dynamic WMI query. Listing 5 shows the update.
Listing 5. New WMI <Query> element value.
<Query>Select * from Win32_Process Where Name = 'Notepad.exe'</Query>
|
The new <Query>
element value will allow us to simplify the resources required to
perform the work that we want to do. Because we only care about the Win32_Process
class—and more specifically, the Notepad.exe process—we will use the
following query only to validate against our desired process. If you
want to use this same query for another process, just change the process
name from Notepad.exe to whatever you want to search for.
Updating the <WMI.ProcessWMIRequest> Method
We need to modify the <ProcessWMIRequest> method to reflect our required change to shut down any process called Notepad.exe. Listing 6 shows the modifications.
Listing 6. Updated <ProcessWMIRequest> method.
Private Sub ProcessWMIRequest()
While Not m_ThreadAction.StopThread
If Not m_ThreadAction.Pause Then
Try
'Now process the file
Connect(m_WMIWorkerOptions.Server, m_WMIWorkerOptions.WMIRoot)
Dim pMOC As ManagementObjectCollection
pMOC = Query(m_WMIWorkerOptions.Query)
If Not pMOC Is Nothing Then
Dim Name As String = Nothing
Dim ProcessId As String = Nothing
Dim pMO As ManagementObject
For Each pMO In pMOC
Try
ReadProperty(pMO, "Name", Name)
ReadProperty(pMO, "ProcessId", ProcessId)
Catch ex As Exception
Exit For
End Try
'Lets Log This Information
Dim pszOut As String
'terminate the process
If Name.Trim = "Notepad.exe" Then
pMO.InvokeMethod("Terminate", Nothing)
End If
pszOut = "Shut down of process Name: " + Name.Trim + vbCrLf
+ "Process ID: " + ProcessId
WriteLogEvent(pszOut, WMI_INFO,
EventLogEntryType.Information, "Tutorials")
pMO.Dispose()
Next
Else
WriteLogEvent("Thread WMI query Error - " + GetError(),
WMI_ERROR, EventLogEntryType.Error, "Tutorials")
End If
pMOC.Dispose()
Catch tab As ThreadAbortException
'Clean up the thread here
WriteLogEvent("Thread Function Abort Error - " + Now.ToString,
WMI_ERROR, EventLogEntryType.Error, "Tutorials")
Catch ex As Exception
WriteLogEvent("Thread Function Error - " + Now.ToString, WMI_ERROR,
EventLogEntryType.Error, "Tutorials")
End Try
End If
If Not m_ThreadAction.StopThread Then
Thread.Sleep(THREAD_WAIT)
End If
End While
End Sub
|
The
bolded code shows that we have modified the code to read the Name and
ProcessId properties. In reality, because we only queried processes
named Notepad.exe, we don’t really need to read the Name property, but
it is always better to be safe and validate a process before you
terminate it.
After we validate that we are looking at an instance of our queried process, Notepad.exe, we invoke the Terminate method of the WMI Win32_Process
class to shut down the process. The last thing we do is write an event
into the Application log to show that we accomplished our goal.
Service Function Validation
To ensure that the service is working properly,
configure and install the service to monitor for required processes. For
each process, add a new entry in the configuration file. Even if the
process is on the same computer, you must add one entry per process. For
each process that your service finds, you should see the process
disappear and then an event appear in the Application log.
You will notice that if a
process requires user interaction, such as saving the current file in
Notepad, you may be requested to do so before the process shuts down.