Implementing WMI
If you're still unclear as to how you could use WMI, press on. As is true of many distributed applications, the example discussed in this article was implemented with server products from multiple vendors; the application also included custom NT services and processes that ran on multiple machines. The end result was a system comprised of twelve NT services and two executable processes running across two NT servers. After the new system was in production, the support team realized that, with all the dependencies, it was going to be difficult to track down problems without a road map that showed the basic information about the core application services. To alleviate the support problem, the development team created a simple COM component in VB that could be called from an ASP page. In order for support personnel to be able to quickly call up the page and determine which components (if any) were having difficulty, the ASP page displays the current status of each of the 14 processes that are required for the system.
A second benefit to using a custom component such as this is that it abstracts the WMI code from the client application, thereby simplifying your client application. In this particular example, the component also allows you to query multiple servers without executing a great deal of WMI code in order to connect to multiple servers.
The WMIProcCheck.Process component developed in VB gathers information about the services and processes to monitor through a simple interface. Then it makes calls to the WMI objects to query for information about each service or process defined in the ASP page. Next the component returns an array or an XML string that contains information about each process, such as its running state, executable path, and security context. In addition, the component contains methods to start and stop the process if it is a service.
To implement the collection of information, the component contains an AddProcess method that takes as arguments the display name for the process, the actual executable file or service name, a flag to determine if the process is a service, and the server name on which to find the process. With each call to this method, the information about the process is saved in a module level multidimensional Variant array.
Public Sub AddProcess(ByVal DisplayName As String, _ ByVal ProcName As String, ByVal IsService As Boolean, _ ByVal ServerName As String) ' Add the process to the Variant array On Error GoTo RedimErr ReDim Preserve mProcesses(6, UBound(mProcesses, 2) + 1) On Error GoTo 0 mProcesses(0, UBound(mProcesses, 2)) = DisplayName 'Name to display mProcesses(1, UBound(mProcesses, 2)) = ProcName 'Actual process or service name mProcesses(2, UBound(mProcesses, 2)) = False 'Running? mProcesses(3, UBound(mProcesses, 2)) = IsService 'Service? mProcesses(4, UBound(mProcesses, 2)) = vbNullString 'Path or startmode mProcesses(5, UBound(mProcesses, 2)) = vbNullString 'Start name mProcesses(6, UBound(mProcesses, 2)) = ServerName 'Server name Exit Sub RedimErr: ReDim mProcesses(6, 0) Resume Next End Sub
Querying WMI for the status of the process is fairly trivial. The component exposes a method called CheckProcesses, which first sorts the array by server name in order to economize on server connections, and then makes a call to a private GetProcs procedure (shown in the following listing).
Check the Processes. The GetProcs private procedure of the component queries for each process or service using WMI and stores the results in the array.
Private Sub GetProcs() Dim objLocator As SWbemLocator Dim objEnumerator As SWbemObjectSet Dim objObject As SWbemObject Dim objServices As SWbemServices Dim strUser As String Dim strDomain As String Dim i As Integer Dim strServer As String Set objLocator = New SWbemLocator ' Loop through the processes For i = 0 To UBound(mProcesses, 2) ' Connect to the server using WMI If objServices Is Nothing Or _ mProcesses(6, i) <> strServer Then Set objServices = Nothing strServer = mProcesses(6, i) Set objServices = objLocator.ConnectServer(strServer) End If If mProcesses(3, i) = False Then ' Process Set objEnumerator = objServices.ExecQuery( _ "Select ExecutablePath From Win32_Process Where Name = '" & _ mProcesses(1, i) & "'") ' See if it's there If objEnumerator.Count = 0 Then mProcesses(2, i) = False mProcesses(4, i) = vbNullString mProcesses(5, i) = vbNullString Else For Each objObject In objEnumerator ' Should only be 1 mProcesses(2, i) = True mProcesses(4, i) = objObject.ExecutablePath objObject.GetOwner strUser, strDomain mProcesses(5, i) = strDomain & "\" & strUser Next End If Else ' Service Set objEnumerator = objServices.ExecQuery( _ "Select State, StartMode, StartName From Win32_Service Where Name = '" & _ mProcesses(1, i) & "'") For Each objObject In objEnumerator ' Should only be 1 mProcesses(4, i) = objObject.StartMode mProcesses(5, i) = objObject.StartName If objObject.state = "Running" Or objObject.state = "Start Pending" Then mProcesses(2, i) = True Else mProcesses(2, i) = False End If Next End If Next ' Clean up Set objLocator = Nothing Set objEnumerator = Nothing Set objObject = Nothing Set objServices = Nothing End Sub
GetProcs traverses the array of processes and uses the ConnectServer method of the SWbemLocator object to connect to the server each time the server name changes. The ConnectServer method returns an object reference of type SWbemServices that represents the available connection to the CIMOM and exposes the CIM classes within the namespace on the server. The core WMI code used to connect to the namespace is as follows:
Dim objServices As SWbemServices Dim objLocator As SWbemLocator Set objLocator = New SWbemLocator If objServices Is Nothing Then Set objServices = objLocator.ConnectServer(strServer) End If
Note that because the ConnectServer call does not contain user name or password information, the security identity of the parent process is used. Once connected to the namespace, you can use the ExecQuery method of the SWbemServices object to pass a WMI Query Language (WQL) string to the server:
Set objEnumerator = objServices.ExecQuery(strWQL)
The query is then parsed, and a collection of SWbemObject object instances is returned in a SWbemObjectSet object. Although ExecQuery is only one of the methods by which to retrieve object instances from WMI, it is a natural interface both for those that are familiar with relational database technology, and in situations such as this where we want to query for a particular set of object instances.
WQL is a SQL-like syntax that contains SELECT, FROM, and WHERE clauses that allow you to specify the properties and classes you would like to retrieve. This component constructs two WQL queries—one to be executed if the process is an executable, and the other to be used if the process is an NT service. For example, to query for an executable process, the component uses the following query:
Select ExecutablePath From Win32_Process Where Name = ?
To query for a service, the component uses the following:
Select State, StartMode, StartName From Win32_Service Where Name = ?
You'll notice that the properties are specified in the SELECT clause, and that the class name is specified in the FROM clause. With each iteration of the loop, GetProcs executes one of these queries and returns a collection of SWbemObject object instances. The collection can then be iterated using standard For Each syntax to inspect each object instance. In this case, GetProcs calls the dynamic properties of the object to retrieve information about the process, such as its State, StartMode, and StartName (for the Win32_Service class, at least). This information is then placed back in the array to be returned to the client as shown below.
Dim objEnumerator As SWbemObjectSet Dim objObject As SWbemObject For Each objObject In objEnumerator mProcesses(4, i) = objObject.StartMode mProcesses(5, i) = objObject.StartName If objObject.State = "Running" Or objObject.State = "Start Pending" Then mProcesses(2, i) = True Else mProcesses(2, i) = False End If Next
You'll notice that although the SWbemObject does not intrinsically support the State, StartMode, and StartName properties (because they are implemented by a specific class), WMI allows you to call them as if they were through automation. After GetProcs has queried for each process, the client can access the updated process information (Variant array) through the Processes method of the component.