The current WMI
implementation isn’t a very scalable solution, so we’ll implement the
same strategies that we used for file monitoring.
Extending the WMI Class
We need to create a more robust and scalable WMI
class that allows for multiple instances of monitored servers. In
addition, we want to be able to predefine a query that we can use to
retrieve specific information.
Because of the extensive nature of changes to the WMI class, I will reprint the code in Listing 1 and then review the changes.
Listing 1. Extended WMI class.
Public Class WMI
Private m_Error As String = Nothing
Private m_Scope As ManagementScope = Nothing
Private m_Path As ManagementPath = Nothing
Private m_Outgoing As Thread = Nothing
Private m_ThreadAction As ThreadActionState
Private m_WMIWorkerOptions As New WMIWorkerOptions
Public Sub New(ByRef threadaction As ThreadActionState,
ByVal wwo As WMIWorkerOptions)
m_ThreadAction = threadaction
WMIWorkerOptions.Copy(m_WMIWorkerOptions, wwo)
m_Path = New ManagementPath
m_Scope = New ManagementScope
End Sub
Public Function Connect(ByVal pszServer As String,
ByVal pszRoot As String) As Boolean
Try
If Not m_Scope Is Nothing Then
Try
If m_Scope.IsConnected Then
Return True
End If
Catch ex As Exception
m_Error = ex.ToString
End Try
End If
m_Path.Path = "\\" + pszServer + pszRoot
m_Scope.Path = Me.Path
m_Scope.Connect()
Return True
Catch ex As Exception
m_Error = ex.ToString
Return False
End Try
End Function
Public Function Query( _
ByVal pszQuery As String) _
As ManagementObjectCollection
Try
If (String.IsNullOrEmpty(pszQuery)) Then
Throw New ArgumentException("pszQuery")
End If
Dim tmpQuery As New ObjectQuery
tmpQuery.QueryString = pszQuery
Dim MOS As ManagementObjectSearcher = New
ManagementObjectSearcher(Me.Scope, tmpQuery)
Dim MOC As ManagementObjectCollection = MOS.Get()
tmpQuery = Nothing
MOS.Dispose()
Return MOC
Catch ex As Exception
m_Error = ex.ToString
Return Nothing
End Try
End Function
Public Function GetError() As String
Try
Return m_Error
Catch ex As Exception
Return ex.ToString
End Try
End Function
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 Domain As String = Nothing
Dim Status As String = Nothing
Dim NumberOfProcessors As String = Nothing
Dim TotalPhysicalMemory As String = Nothing
Dim pMO As ManagementObject
For Each pMO In pMOC
Try
ReadProperty(pMO, "Name", Name)
ReadProperty(pMO, "Domain", Domain)
ReadProperty(pMO, "Status", Status)
ReadProperty(pMO, "NumberOfProcessors",
NumberOfProcessors)
ReadProperty(pMO, "TotalPhysicalMemory",
TotalPhysicalMemory)
Catch ex As Exception
Exit For
End Try
'Lets Log This Information
Dim pszOut As String
pszOut = "Name: " + Name.Trim
pszOut += ",Domain: " + Domain.Trim
pszOut += ",Status: " + Status.Trim
pszOut += ",NumberOfProcessors: " +
NumberOfProcessors.Trim
pszOut += ",TotalPhysicalMemory: " +
TotalPhysicalMemory.Trim + vbCrLf
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
Private Sub ReadProperty(ByRef pObject As ManagementObject, ByRef pProp As
String, ByRef pszStr As String)
Try
pszStr = pObject.Properties(pProp).Value.ToString
Catch ex As Exception
pszStr = ""
End Try
End Sub
Public ReadOnly Property Outgoing() As Thread
Get
Return m_Outgoing
End Get
End Property
Public Sub Start()
m_Outgoing = New Thread(AddressOf ProcessWMIRequest)
m_Outgoing.Priority = ThreadPriority.Normal
m_Outgoing.IsBackground = True
m_Outgoing.Name = m_WMIWorkerOptions.Server + "-" +
m_WMIWorkerOptions.Query
m_Outgoing.Start()
End Sub
Private Shared Sub WriteLogEvent(ByVal pszMessage As String, ByVal dwID As
Long, ByVal iType As EventLogEntryType, ByVal pszSource As String)
Try
Dim eLog As EventLog = New EventLog("Application")
eLog.Source = pszSource
Dim eInstance As EventInstance = New EventInstance(dwID, 0, iType)
Dim strArray() As String
ReDim strArray(1)
strArray(0) = pszMessage
eLog.WriteEvent(eInstance, strArray)
eLog.Dispose()
Catch ex As Exception
'Do not Catch here as it doesn't do any good for now
End Try
End Sub
Public ReadOnly Property Scope() As ManagementScope
Get
Return m_Scope
End Get
End Property
Public ReadOnly Property Path() As ManagementPath
Get
Return m_Path
End Get
End Property
End Class
|
New WMI Member Variables
As shown in Listing 2,
we are going to add several new WMI member variables, which will hold
the pointers to our internal WMI implementation, and our new
thread-state management class instance.
Listing 2. WMI class member variables.
Private m_Error As String = Nothing
Private m_Scope As ManagementScope = Nothing
Private m_Path As ManagementPath = Nothing
Private m_Outgoing As Thread = Nothing
Private m_ThreadAction As ThreadActionState
Private m_WMIWorkerOptions As New WMIWorkerOptions
|
As you can see, we have only one thread per WMI instance because no e-mail or incoming file processing is required.
New and Updated WMI Class Member Methods
The new WMI class implementation has several
properties that are used to relate to the WMI Management namespace in
Microsoft Visual Studio. The following list includes these changes to
the WMI class and a brief description of how they correlate to their WMI
representations.
The <New> Constructor
Because we are now using thread state management, I have updated the <New> constructor so that, we can pass in a shared instance of the thread state management object, as well as the newly created WMIWorkerOptions class.
The WMIWorkerOptions Class
Note that the WMIWorkerOptions class is very close in design and detail to the FileWorkerOptions class. It is used for the same purpose—to store WMI specific configuration information on a per-class instance basis.
The WMI Properties
As with the FileWorker class, we need to store certain pieces of information outside the scope of our WMIWorkerOptions class, so we will implement it directly in our WMI class.
The WMI Path
The WMI path represents the physical and logical connection to the
local or remote computer where the WMI classes that you want to work
with are implemented. ManagementPath is given a string to represent the
FQDN of the root node and object you want to query. In our class, we
defined this as the property Path.
The WMI Scope
The WMI scope represents the connection to the root node as defined in
the ManagementPath variable associated with the scope. This is where you
will implement the underlying details of the connection to the computer
and therefore to the WMI objects you want to interface with. In our
class, we define this as the property Scope.
The <ProcessWMIRequest> Method The <ProcessWMIRequest> is like the <ProcessFiles> method in our FileWorker
class. It defines the entry point for the thread created on a per-WMI
instance basis. This is where we connect to the local or remote WMI
services, query the data we are looking for, and then process the data.
Creating the WMIWorkerOptions Class
We
need to be able to pass into our new WMI class the properties that we
will define in our configuration file, which we will discuss in the next
section. For this I have created the class shown in Listing 3.
Listing 3. WMIWorkerOptions class.
Public Class WMIWorkerOptions
Private m_WMIRoot As String
Private m_Query As String
Private m_Server As String
Public Property Server() As String
Get
Return m_Server
End Get
Set(ByVal value As String)
m_Server = value
End Set
End Property
Public Property Query() As String
Get
Return m_Query
End Get
Set(ByVal value As String)
m_Query = value
End Set
End Property
Public Property WMIRoot() As String
Get
Return m_WMIRoot
End Get
Set(ByVal value As String)
m_WMIRoot = value
End Set
End Property
Public Shared Sub Copy(ByVal wwo1 As WMIWorkerOptions,
ByVal wwo2 As WMIWorkerOptions)
wwo1.Query = wwo2.Query
wwo1.Server = wwo2.Server
wwo1.WMIRoot = wwo2.WMIRoot
End Sub
End Class
|
The Query Property
The Query property represents the WMI query
that will be passed to this instance from our configuration file. The
query may differ slightly based on the operating system.
The WMIRoot Property
The
WMIRoot property represents the root node of the local or remote WMI
class instance that you want to query. From here you can query or use
other subordinate classes or methods.
The Server Property
The Server property represents the server that
we want to connect to. It can also represent the local computer by
either using the local computer name or by using a period, which also
represents the local computer.
Creating the Configuration File
We will create a configuration file that we can use to identify the preceding properties to our WMI class instances. Listing 4 shows the XML file that we are using.
Listing 4. WMI configuration XML file.
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<WMIWorkerOptions>
<WMIWorkerProperties>
<Query>select * from Win32_ComputerSystem</Query>
<WMIRoot>\root\cimv2</WMIRoot>
<Server>mgern-D820</Server>
</WMIWorkerProperties>
<WMIWorkerProperties>
<Query>select * from Win32_ComputerSystem</Query>
<WMIRoot>\root\cimv2</WMIRoot>
<Server>TestServer</Server>
</WMIWorkerProperties>
</WMIWorkerOptions>
</Configuration>
|
The configuration file shown previously in Listing 3 contains the matching properties from our WMIWorkerOptions class. You will notice that I have included the WMIWorkerOptions
section twice. This is to test that we can get multiple threads to
perform the work. Future implementations of the service can either point
to different servers or use different queries to customize the service
usage.
WMI Service Account
WMI communicates over RPC to the local and
remote servers. You need to ensure that on the computer you want to
communicate with, the service or the threads using WMI are running in
the context of a user with the proper privileges.
After installing the
service and implementing your own configuration file, you will see at
least one Application log entry for each entry in the configuration
file. The intent here is to show that you can use WMI to query both
local and remote information.