한국   대만   중국   일본 
Programming with Windows Management Instrumentation | Programming with Windows Management Instrumentation
InformIT

Programming with Windows Management Instrumentation

Date: Oct 4, 2000

Return to the article

Dan Fox walks you through a simple development project that shows you the capabilities of Windows Management Instrumentation (WMI).

Dan Fox walks you through a simple development project that shows you the capabilities of Windows Management Instrumentation (WMI).

Although it hasn't been focused on as often as other technologies have, Microsoft has included a gem in Windows 2000 that finally makes it easy to gather configuration information programmatically from one or more computers. This revolution?which goes by the handle WMI?consists of a standard API that exposes a myriad of settings that are accessible from any automation-capable client, such as VB, VBA, and ASP. While you may have had some inkling about the capabilities of WMI, it may not be entirely clear how you as a VB developer can take advantage of this technology. So, before you hit the back button on your browser because you consider WMI to be for system administrators and other network geeks only, let me show you a simple solution that utilizes VB and WMI in a real development project.

WBEM to the Rescue

Before we get to WMI, we need to briefly review its ancestry. Microsoft developed WMI as the implementation of an industry initiative known as Web-Based Enterprise Management (WBEM). The purpose of this initiative was to create a single set of Internet-based standards for managing devices and systems on an enterprise network in order to supplant technologies such as Simple Network Management Protocol (SNMP) and Desktop Management Interface (DMI). The WBEM initiative is now in the hands of the Distributed Management Task Force (DMTF). (For more information on DMTF, see www.dmtf.org/wbem .)

WBEM is comprised of several technologies, the most important being CIM (Common Information Model). CIM is an object-oriented data model that is made up of instances, methods, and properties; this technology is used to describe management information in an enterprise network. The specification is implementation and vendor-neutral, and is expressed through Managed Object Format (MOF) files. MOF files are text files that contain the definition of classes or actual object instances that represent aspects of an enterprise network. For example, the DMTF has defined five common schemas for systems, applications, databases, networks, and devices. The schemas contain classes (all prefixed with CIM?such as CIM_NetworkAdapter? in order to indicate that they are standard) that represent devices, as shown here:

class CIM_NetworkAdapter : CIM_LogicalDevice
{
      [MaxLen (64), Description (
          "PermanentAddress defines the network address hardcoded into "
          "an adapter. This 'hardcoded' address may be changed via "
          "firmware upgrade or software configuration. If so, this field "
          "should be updated when the change is made. PermanentAddress "
          "should be left blank if no 'hardcoded' address exists for the "
          "NetworkAdapter."),
        ]    
    string PermanentAddress;
        [MaxLen (64), 
         Description (
          "An array of strings indicating the network addresses for an "
          "adapter."),
         ArrayType ("Indexed")
      ]
    string NetworkAddresses[]; 
        [Description (
          "An estimate of the current bandwidth in Bits per Second. "
          "For Adapters which vary in bandwidth or for those where "
          "no accurate estimation can be made, this property should "
          "contain the nominal bandwidth."),
         Units ("Bits per Second") 
        ]
    uint64 Speed;
        [Description ( 
         "The maximum speed, in Bits per Second, for the Network"
         "Adapter."), 
         Units ("Bits per Second") 
        ] 
    uint64 MaxSpeed;
      [Description (
        "Boolean indicating that the Adapter is operating in "
        "full duplex mode.")
      ]
   boolean FullDuplex;
        [Description ( 
          "A boolean indicating whether the Network Adapter is capable " 
          "of automatically determining the speed or other communications "
        "characteristics of the attached network media.") 
        ] 
    boolean AutoSense;
        [Description (
          "The total number of octets transmitted, including framing "
          "characters."),
       Counter
      ] 
    uint64 OctetsTransmitted;
        [Description (
          "The total number of octets received, including framing "
          "characters."),
       Counter
      ] 
    uint64 OctetsReceived;
};

In this case, the CIM_NetworkAdapter class inherits from CIM_LogicalDevice and contains properties of the adapter. In addition, vendors can inherit from these classes and extend them if their particular hardware or software supports features that are not present in the standard classes. For example, Microsoft extends many of the standard classes by prefixing the class name with "Win32"?the Microsoft class for network adapters is then called Win32_NetworkAdapter. For more information on CIM schemas, see www.dmtf.org/spec/cim_schema_v23.htm .

CIM schemas define the standard set of data, but not the implementation. As a result, it's up to hardware and software vendors to implement these standards. That's where WMI comes in.

WMI Overview

Simply put, WMI is the COM-based implementation of WBEM for the Windows platform. WMI uses the familiar Windows Open Systems Architecture (WOSA) model to abstract management applications from the underlying hardware or software that uses a set of DCOM interfaces and a set of supplied providers (as shown in Figure 1 ). Some of these providers query their respective devices at system startup and place static information about the devices in the CIM Object Repository (located at \winnt\system32\wbem\repository) using the CIM Object Manager (CIMOM); others query the devices dynamically when the application calls for information. A system service (WinMgmt.exe) runs as a background process to facilitate communication between the various components. This open architecture allows third parties to develop WMI providers for their own hardware and software to seamlessly integrate with Windows. For a discussion on writing your own CIM provider, see "Say Goodbye to Quirky APIs: Building a WMI Provider to Expose Your Object Info" on MSDN .

In Figure 1 , you'll notice that Microsoft provides a large set of classes that are ready to use against the Windows platform. WMI also aggregates classes into collections called namespaces that allow classes to be uniquely identified within the namespace.

Figure 1

WMI Architecture. Management applications use the WMI automation objects to communicate with the WMI interfaces and WinMgmt.exe service. Providers are used to abstract the hardware- or software-specific information.

The WMI automation object model sits on top of the WMI DCOM interfaces and contains a simple set of generic objects, prefixed with "SWbem," that wrap the underlying COM interfaces. The most important of these objects?and the ones I'll show you in the example code?are listed in the following table:

Object

Description

SWbemLocator

Used to obtain a reference to an SWbemServices object that represents the services within a namespace on a particular machine. Has ConnectServer and Security members to handle the connection and manipulate security.

SWbemServices

Represents the services within a namespace. Contains methods such as ExecQuery , ExecMethod , and InstancesOf to query and manipulate specific class instances.

SWbemObject

Represents a single WMI object instance of a class definition. Contains both generic and dynamic properties used to manipulate the object. The dynamic properties use automation to provide access to methods and properties of the specific class.

SWbemObjectSet

Is a collection of SWbemObject object instances that is returned through methods such as SWbemServices.ExecQuery .

These objects are interesting because they use the SWbemLocator object to provide generic access to any class in the namespace to which you connect, while simultaneously providing dynamic binding for method and property access. This architecture simplifies the writing of management applications that query a variety of different information. You'll also note from the table that the ConnectServer method can be used to explicitly pass the security credentials you would like to use in subsequent calls to WMI. Because WMI uses the security context of the current process by default, this is particularly useful when using WMI from ASP scripts on IIS 4.0. In addition, the Security object can be used to set the authentication and impersonation levels that will be used by default.

Obviously, there is much more to be said about WMI, but I'm sure that you're anxious to get to the code. To avoid going into extensive detail, I'll simply recommend two excellent articles that will provide you with additional background information: "Windows Management Instrumentation: Administering Windows and Applications Across Your Enterprise," which is located on MSDN ( msdn.microsoft.com/library/periodic/period00/wmiover.htm ), and "Manage Resources with WMI," which is found in the July issue of the Visual Basic Programmers Journal .

Before you get started, you'll want to download the WMI SDK from msdn.Microsoft.com/downloads/sdks/wmi/download.asp . The SDK contains additional classes, sample applications, and a set of ActiveX controls you can embed in your own applications. Although WMI 1.5 ships with Windows 2000, NT developers can also take advantage of WMI by downloading and installing the WMI Core Software Installation on NT 4.0 found at the preceding link. Remember, though, that each server you want to query with WMI must have the core components installed.

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.

Calling the Component

In order to call the WMIProcCheck.Process component, you may use code like the following to determine if notepad.exe and SQL Server were running on the server "MyServer":

Dim objProc As WMIProcCheck.Process
Dim i as integer

Set objProc = New WMIProcCheck.Process

With objProc
       .AddProcess "Notepad", "notepad.exe", False, "MyServer"
       .AddProcess "SQL Server", "mssqlserver", True, "MyServer"
End With

objProc.CheckProcesses

For i = 0 to Ubound(objProc.Processes,2)
       MsgBox objProc.Processes(0,i), 0, objProc.Processes(2,i)
Next i

Note that the array returned by objProc.Processes returns the running state in the third element (ordinal 2) of the array. The sample application in the download package includes a simple UI (shown in Figure 2 ) for exercising the component.

Figure 2

Exercise the Component. This simple UI allows the user to specify which processes to check, and subsequently shows the results in the list view control.

Adding Some Extras

The WMIProcCheck.Process component also provides the ability to start and stop a service using its StartProc and StopProc methods. Both methods accept the index into the processes array in order to indicate which process to stop. Rather than use ExecQuery and WQL to obtain a reference to the SWbemObject object, these methods use the alternative Get method of the SWbemServices object. The Get method is passed the equivalent of the WHERE clause, which contains the class name and returns the reference. In the following code snippet the variable ServiceName is assumed to hold the name of an NT Service to query for.

Dim ServiceObject As SWbemObject
Dim ServiceName As String

Set ServiceObject = objServices.Get("Win32_Service='" & ServiceName & "'")
ServiceObject.StartService

After the reference is obtained, you can once again call a method on the class(such as StartService ) dynamically through automation. Note that for this component, the StartProc and StopProc methods simply return as False if the process is not an NT service.

The custom component's second feature is that it can return the information about the processes in an XML string as well as in an array. This is obviously beneficial for returning information through an ASP page that is to be displayed in MSIE. The GetXML method of the component uses the MSXML parser to create a simple XML document by looping through the array and creating elements with the appropriate attributes for each process as shown in the Listing below. The resulting XML for the notepad and SQL Server processes (discussed previously) follows:

<ProcessData>
     <Process DisplayName="notepad" ProcName="notepad.exe" 
        Running="-1" IsService="0" PathMode="C:\WINNT\System32\notepad.exe"
        Account="SSOSA\Administrator" ServerName="ssosa"/>   
     <Process DisplayName="SQL Server" ProcName="MSSQLServer" 
       Running="-1" IsService="-1" PathMode="Auto" Account="LocalSystem"
       ServerName="ssosa"/>
</ProcessData>

Get the XML. The GetXML public function uses the MSXML parser to create an XML document from the internal array.

Public Function GetXML() As String

' Return the data as XML
Dim xmlDoc As DOMDocument
Dim xmlElem As IXMLDOMElement
Dim xmlSubElem As IXMLDOMElement
Dim i As Long

If IsArray(mProcesses) Then

    Set xmlDoc = New DOMDocument

    ' Create the top level element
    Set xmlElem = xmlDoc.createElement("ProcessData")

    ' Loop through the processes and create the sub elements
    For i = 0 To UBound(mProcesses, 2)
        Set xmlSubElem = xmlDoc.createElement("Process")
        xmlSubElem.setAttribute "DisplayName", mProcesses(0, i)
        xmlSubElem.setAttribute "ProcName", mProcesses(1, i)
        xmlSubElem.setAttribute "Running", mProcesses(2, i)
        xmlSubElem.setAttribute "IsService", mProcesses(3, i)
        xmlSubElem.setAttribute "PathMode", mProcesses(4, i)
        xmlSubElem.setAttribute "Account", mProcesses(5, i)
        xmlSubElem.setAttribute "ServerName", mProcesses(6, i)
        xmlElem.appendChild xmlSubElem
        Set xmlSubElem = Nothing
    Next i
    
    ' Append the high level node
    xmlDoc.appendChild xmlElem
    GetXML = xmlDoc.xml
    Set xmlDoc = Nothing
    
Else
    Set xmlDoc = Nothing
    Err.Raise vbObjectError + 5002, "GetXML", "No processes to enumerate"
End If
End Function

In this case, both notepad and SQL Server were found running. Keep in mind that the DMTF is adding XML support to the specification, so look for Microsoft to add intrinsic XML support to the WMI interfaces shortly.

Although this article is not an exhaustive discussion of WMI, this small example is intended to help you understand you can use WMI to your advantage.

800 East 96th Street, Indianapolis, Indiana 46240