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.