| Printable Version
An in-depth look at WMI and instrumentation, Part II
By Klaus Salchner
Introduction
The first part of this article explained how to instrument your application.
We looked at a simple WMI provider to demonstrate how to define WMI classes,
how the .NET framework registers these classes in the WMI store, how to
publish class instances and how to raise WMI events. On the other hand we
looked at a simple WMI consumer to demonstrate how to query for WMI class
instances and how to subscribe to and receive asynchronously WMI events.
Finally we looked at Microsoft Operations Manager 2005, how to set it up to
monitor WMI events raised by instrumentation applications and how to create
Management Packs to distribute to customers. This gives a comprehensive view
about how to instrument applications and how to tie this in with management
applications like MOM 2005.
This second part looks in more detail at how to query the WMI store, how to
work with WMI classes and class instances, and then demonstrates the wealth
of information available through the Win32 and IIS WMI providers. This
article has two sample applications attached. The "WMI System Browser"
allows you to browse a wealth of information about your machine. The "WMI
Power
Browser" allows you to browse the WMI schema and look in detail at classes
and
class instances. All .NET types for working with WMI reside in the
System.Management DLL, so you need to add the System.Management namespace to
your code.
Working with WMI namespaces
WMI supports namespaces, allowing users to logically group WMI classes
together. Each WMI provider normally registers its own WMI namespace and
then all its classes within that namespace. For example, all Win32 WMI
classes can be found in the namespace "root\cimv2", all IIS WMI classes can
be found at "root\microsoftiisv2", and all LDAP WMI classes can be found at
"root\directory\ldap". The root namespace is called "Root" and namespaces
can have child namespaces and WMI classes. Each namespace contains a class
called __namespace and its class instance list gives you a list of all child
namespaces. Here is a code snippet for that:
// the root namespace we are connecting to
ManagementScope Scope
= new ManagementScope(@"\.\Root");
// the __namespace class to connect to -
which provides a
list of
// all child namespaces in the namespace we are connecting to
ManagementPath
ClassPath =
new ManagementPath("__NAMESPACE");
// connect to the namespace and class
ManagementClass
WMIParentObject = new ManagementClass(Scope, ClassPath, null);
try
{
// now loop through all the object instances we find for that
namespace class
foreach (ManagementObject WMIChild in WMIParentObject.GetInstances())
{
// a child-namespace found
string ChildNamespace =
Convert.ToString(WMIChild.Properties["Name"].Value);
// add the namespace name to the
tree view
TreeNodeCol.Add(WMIChild.Path.Path, ChildNamespace.ToLower());
// release the underlying COM
object
WMIChild.Dispose();
}
}
// show any exception happening
catch (Exception
e)
{
MessageBox.Show(e.Message);
}
// release the underlying COM object
finally
{
WMIParentObject.Dispose();
}
It first creates a ManagementScope object and sets it to the root namespace
on the local machine. This defines where (which machine and namespace) the
class we are binding to is residing. Next we create a ManagementPath setting
it to the WMI class we are binding to (the __namespace class). Finally we
create a ManagementClass object using the scope and path object we created.
The same could be achieved by only creating a ManagementClass object and
passing along the path "\.\root:__namespace". When you have bound to a WMI
class, you can call GetInstances() to get a list of all class instances
for this class. In the case of the __namespace class this is the list of
child
namespaces. The path and the name of each namespace is then used to add it
to a tree view. The ManagementClass and ManagementObject classes use
underlying unmanaged COM objects, so call Dispose to release them.
The WMI infrastructure allows you to apply security to each namespace. Open
up
the "Computer Management" console in Windows (right click on "My Computer"
and choose Manage
from the popup menu) and navigate to the item "Service
and Applications" and then "WMI Control". Open up the properties of the "WMI
Control" item and navigate to the "Security" tab. It allows you to navigate
the namespace hierarchy (shown in a tree view). Select the one you want to
set the security for and click on the "Security" button. This brings up the
standard Windows ACL dialog and allows you to set the rights for each
Windows user or
Windows group. The available rights are:
- Execute Methods
Allows user to execute methods defined on the
WMI
classes.
- Full Write
- Edit Security information
This allows you to set granular access rights for the classes and class
instances in this namespace. The list of security rights shown is on Windows
2003. On Windows XP, "Enable Account" is named "Enable" and "Full Write" is
named "Full Control". All other security rights are named the same.
Working with WMI classes
The class ManagementClass allows you to bind to and work with WMI classes.
The path of a WMI class consists of the machine, followed by the namespace
hierarchy ending with a colon and the class name itself, for example
"\.\Root\cimv2:Win32_Volume". The ManagementClass provides access to the
following information:
- SystemProperties an array of strings.
- __Dynasty example __SystemClass. This value is the same as __Derivation if the class
inherited from has no parent class, such as the class __Provider
(inherits from __SystemClass which has no parent class).
- __Path
- __Relpath which excludes the machine and namespace (this is everything after the
colon of the full path). In our example this is __Win32Provider.
- __Namespace
- __Server
- __Property_Count
This collection is of the type PropertyDataCollection which is a collection
of PropertyData types. The type PropertyData exposes Name and Value
properties. The property
IsArray can be used to determine whether Value is an array of values or
a single value.
Properties writing property values on a class instance. This is also of the type
PropertyDataCollection, which is again a collection of PropertyData types.
Qualifiers href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/wmi_qualifiers.asp">
here. Some examples are:
These four collections provide you with a wealth of information about the
WMI class. They can be used to read information about a WMI class and also
to edit a WMI class (shown by the attached "WMI Power Browser" application).
Here is a code snippet illustrating how to read class information.
ManagementClass WMIClass = null;
try
{
WMIClass = new ManagementClass(@"\.\root\cimv2:__Win32Provider");
// add all the properties to the list
foreach (PropertyData
Property in WMIClass.Properties)
ListCollection.Add(new
ListViewItem(new
string[] { "Property",
Property.Name,
GetPropertyValue(Property)
}));
// add all the system properties to the list
foreach (PropertyData
Property in
WMIClass.SystemProperties)
ListCollection.Add(new
ListViewItem(new
string[] { "SystemProperty",
Property.Name,
GetPropertyValue(Property)
}));
// add all the qualifiers to the list
foreach (QualifierData Qualifier in
WMIClass.Qualifiers)
ListCollection.Add(new
ListViewItem(new
string[] { "Qualifier",
Qualifier.Name,
Convert.ToString(Qualifier.Value) }));
// add all the methods to the list
foreach (MethodData Method in
WMIClass.Methods)
ListCollection.Add(new
ListViewItem(new
string[] { "Method",
Method.Name,
String.Empty }));
}
// show any exception happening
catch (Exception
e)
{
MessageBox.Show(e.Message);
}
// release the underlying COM object
finally
{
if (WMIClass != null)
WMIClass.Dispose();
}
This code snippet binds to the __Win32Provider class in the root\cimv2
namespace and then reads all its properties, system properties, qualifiers
and methods and adds for each the name and value to a list view (note that
methods have no values). At the end it calls Dispose to release the
underlying COM object. The value of properties or system properties can be
arrays so it calls GetPropertyValue() which does the following:
public
string
GetPropertyValue(PropertyData Property)
{
// a single value property so we return its value as string value
if (!Property.IsArray)
return Convert.ToString(Property.Value);
// return a comma separated list of array values
else
{
string PropertyValue =
String.Empty;
// loop through all items in the array; only if we have items (!= null)
if (Property.Value !=
null)
{
foreach (object
Value in ((Array)Property.Value))
PropertyValue += Convert.ToString(Value) + ", ";
}
// return the comma separated list
of values for this
array
if (PropertyValue.Length >
0)
return
PropertyValue.Substring(0, PropertyValue.Length - 2);
else
return String.Empty;
}
}
It checks through IsArray if this is an array or single value. If a single
value it converts the value to a string and returns it. For an array of
values it loops through each item and adds it to a comma-separated list
which is then returned.
Working with WQL queries
You can query WMI using the WQL query language. WQL is based on the standard
SQL syntax with some differences, the biggest one being that it allows only
queries and not updates, inserts or deletes. It allows you to query
for classes, class instances, class associations, inherited classes,
etc. Queries are always executed against a namespace. Here are a few
examples:
- SELECT * FROM meta_class
Queries for all classes in the namespace.
- SELECT * FROM meta_class WHERE __CLASS = "Win32_Volume"
Queries for a class with the name Win32_Volume.
- SELECT * FROM meta_class WHERE __this ISA "Win32_Volume"
Queries for a class with the name Win32_Volume and all its child classes. So
any class which directly or indirectly is inherited from this class is
returned.
- SELECT * FROM Win32_Volume WHERE __CLASS = "Win32_Volume"
Queries for any class instances of the class Win32_Volume.
- SELECT * FROM Win32_Volume
Queries for any class instances of the class Win32_Volume and any other
class directly or indirectly inherited from it.
To execute a WQL query create first a ManagementScope object which defines
against which namespace you want to perform this query, for example
"\.\root\cimv2". Next create an instance of WqlObjectQuery and pass along
the query WQL string. Finally create an instance of ManagementObjectSearcher
and pass along the scope and query objects. Call
ManagementObjectSearcher.Get() which performs the search and returns a
collection of ManagementObject objects. Here is a sample code snippet:
// the namespace we are searching in
ManagementScope
Scope
= new ManagementScope(@"\.\root\cimv2");
// the query we are performing
WqlObjectQuery Query
=
new WqlObjectQuery("SELECT * FROM meta_class");
// enumeration options to use for the search; set a time-out value
EnumerationOptions
Options = new EnumerationOptions();
Options.Timeout = new
TimeSpan(0, 0, 10);
// instantiate the searcher object using the scope and query
ManagementObjectSearcher Searcher = new ManagementObjectSearcher(Scope, Query, Options);
try
{
// now get the search results and loop through all found object
instances
foreach (ManagementObject Object in
Searcher.Get())
{
// add the object to the list
view
Collection.Add(new
ListViewItem(new
string[] { Object.Path.Path,
Object.Path.RelativePath } ));
// dispose the object instance
Object.Dispose();
}
}
// show any exception happening
catch (Exception
e)
{
MessageBox.Show(e.Message);
}
// dispose the underlying search COM
object
finally
{
Searcher.Dispose();
}
This code snippet sets the search scope to the namespace "root\cimv2" and
searches for all classes in that namespace. It also sets a timeout of 10
seconds for enumerating the result-set, not for how long
the actual search is allowed to take. It adds for each found object the full
and relative path to a list view. Don't forget to call Dispose() on each
object in the enumeration and on the ManagementObjectSearcher object to
release
the underlying COM objects.
You can also build associations between different classes using association
classes. Association classes are classes which have references to two other
classes and through that associate these two classes. The diagram below
shows the associations between the Win32_DiskPartition class with the
Win32_ComputerSystem, Win32_DiskDrive and Win32_LogicalDisk class. For each
there is an association class:
|
- Win32_SystemPartitions computer system. The GroupComponent property on this class contains the
reference to the Win32_ComputerSystem class while the PartComponent property
contains the reference to the Win32_DiskPartition class.
- Win32_DiskDriveToDiskPartition partition. The Antecedent property on this class contains the reference to
the Win32_DiskDrive class while the Dependent property the reference to the
Win32_DiskPartition class.
- Win32_LogicalDiskToPartition a partition. The Dependent property on this class has the reference to the
Win32_LogicalDisk class and the Antecedent property the reference to the
Win32_DiskPartition class.
|
WMI allows you to query for those associated classes and the association
classes which reference the two classes together. This makes it easy to find
all associations and association classes without having to go through the
documentation of all classes, which in the case of "root\cimv2" number in
the
hundreds. Let's assume your computer has an instance of the
Win32_DiskPartition class with the value "Disk #0, Partition #0" and you
want to find all associated classes and class instances and association
classes and class instances. Here is how it works:
- ASSOCIATORS OF {Win32_DiskPartition.DeviceID="Disk #0, Partition
#0"}
Returns all class instances which are associated with this
Win32_DiskPartition class instance. The result may vary based on your
hardware and software configuration. My machine returns an instance of the
Win32_ComputerSystem class (value Enterprise-Minds), an instance of the
Win32_LogicalDisk class (value "C:") as well as an instance of the
Win32_DiskDrive class (value "\.\PHYSICALDRIVE0"). Note that
Win32_DiskDrive is your physical disk drive while Win32_LogicalDiskDrive is
the logical disk created through your OS.
- ASSOCIATORS OF {Win32_DiskPartition.DeviceID="Disk #0, Partition #0"}
WHERE ClassDefsOnly
This query returns the WMI class names of all the classes associated with
the Win32_DiskPartition class. My machine returns Win32_ComputerSystem,
Win32_DiskDrive and Win32_LogicalDiskDrive.
- REFERENCES OF {Win32_DiskPartition.DeviceID="Disk #0, Partition
#0"}
Returns all the association class instances which have a reference to this
Win32_DiskPartition class instance, meaning one of the properties contains a
reference to this class instance. My machine returns an instance of the
Win32_DiskToDiskPartition class, an instance of the Win32_SystemPartitions
class and an instance of the Win32_LogicalDiskToPartition class.
- REFERENCES OF {Win32_DiskPartition.DeviceID="Disk #0, Partition #0"}
WHERE ClassDefsOnly
This query returns the class names of all the associations classes
referencing the Win32_Partitions class. My machine returns
Win32_DiskDriveToDiskPartition, Win32_LogicalDiskToPartition and
Win32_ComputerSystem.
WMI provides a wealth of information, so it is important to have strong
query/search capabilities and to be able to get a good view of the
associations between the data. WQL is build on top of SQL which makes the
query language instantly familiar to developers but is still enhanced to
support WMI specific capabilities. You can find out more about WQL from
here as well as
here
.
Working with WMI class instances
The ManagementObject class can be used to work with WMI class instances. The
ManagementClass type is inherited from the ManagementObject type. This means
nearly
all the functionality explained in the section "Working with WMI classes"
applies also to WMI class instances. The two exceptions are that the
property values for a class are default values (if specified) while they
return the actual value for a class instance, and there is no Methods
collection, meaning there is no access to the method information. You can
obtain the class information of a class instance through the ClassPath
property. This returns a ManagementPath type which provides access to the
class name (ClassName property), the server queried (Server property), the
namespace (property NamespacePath), the full path (Path property) and the
relative path (RelativePath property).
Through ManagementObject.InvokeMethod() you can invoke a method on the class
instance. Managed WMI classes (i.e., those provided by managed code) cannot
provide methods. But many of the unmanaged WMI classes provide methods which
you can invoke to manipulate the class instance. For example the class
Win32_Volume has a Chkdsk() method to perform a check disk, a
DefragAnalysis() method to perform defrag analysis, a Defrag() method to
perform a defrag and many more. Query the WMI class to obtain a list of
methods. When you know the method name use the method
ManagementObject.GetMethodParameters() to obtain a ManagementBaseObject with
the list of in parameters for the method. Next set the values for all in
parameters and then call ManagementObject.InvokeMethod() to invoke the
method. It returns another ManagementBaseObject type which contains the
values of all the out parameters plus the method return value. The method
return value is stored in the out parameter list as an out parameter with
the name ReturnValue. Here is a code snippet:
ManagementObject
WMIObject =
null;
string ObjectPath =
@"\.\root\cimv2:Win32_Proxy.ServerName=""Enterprise-Minds""";
string MethodName =
"SetProxySetting";
try
{
// bind to the WMI object instance
WMIObject
= new ManagementObject(ObjectPath);
// get a WMI object representing all in-parameters for this method
ManagementBaseObject InParams =
WMIObject.GetMethodParameters(MethodName);
// set all the in-param values according to the in-param list view
foreach (ListViewItem
Item in InParamItems)
InParams[Item.SubItems[0].Text] = Item.SubItems[1].Text;
// set the method invoke options so that the method has 10 sec to
execute
InvokeMethodOptions Opt = new
InvokeMethodOptions();
Opt.Timeout = new TimeSpan(0,
0, 10);
// execute the method on the WMI object and get the out-parameters
ManagementBaseObject OutParams =
WMIObject.InvokeMethod(MethodName, InParams, Opt);
// read all the return values and show them in the out-param list view
foreach (PropertyData
Property in
OutParams.Properties)
OutParamItems.Add(new
ListViewItem(new
string[] { Property.Name,
GetPropertyValue(Property) }));
// dispose the COM object of the in param object
if (InParams != null)
InParams.Dispose();
// dispose the COM object of the out param object
if (OutParams != null)
OutParams.Dispose();
}
// show any error message
catch (Exception
e)
{
MessageBox.Show(e.Message);
}
// free up the underlying COM object
finally
{
if (WMIObject != null)
WMIObject.Dispose();
}
The code snippet first binds to the Win32_Proxy class instance (note that
Enterprise-Minds is the machine name, which will of course be different for
your
machine) and then gets the method in parameters from the
GetMethodParameters() method. It then reads all the in parameter values from
the InParamItems list view. Then it invokes the InvokeMethod() which returns
all the out parameters and the method return value. These are then added to
an OutParamItems list view. At the end it calls Dispose on the in parameter
and out parameter types and the class instance itself to release the
underlying COM objects. Note that some out parameter values might be another
WMI class instance. The "WMI Power Browser" sample application attached
shows how to check for the type of the return value and then read the
property values of such a returned WMI class instance.
You can also delete class instances, unless they are dynamic. First bind to
the class instance and then call the method ManagementObject.Delete(). Here
is a code snippet for that:
ManagementObject WMIObject =
null;
try
{
WMIObject = new ManagementObject(ObjectPath);
// delete the object
WMIObject.Delete();
}
// show any exception happening
catch (Exception
e)
{
MessageBox.Show(e.Message);
}
// release the underlying COM object
finally
{
if (WMIObject != null)
WMIObject.Dispose();
}
Property values of a class instance can be updated. These changes will not
be committed till you call ManagementObject.Put(). The provider for dynamic
class instances needs to support write access to the property. No managed
class instances support write access. Here is a code snippet showing how to
update property values:
ManagementObject
WMIObject =
null;
try
{
WMIObject = new ManagementObject(ObjectPath);
// loop through all the properties in the list view and get the
values
foreach (ListViewItem
Item in ItemCollection)
WMIObject.Properties[Item.SubItems[0].Text].Value =
Item.SubItems[1].Text;
// write any changes back to the WMI store
WMIObject.Put();
}
// show any exception happening
catch (Exception
e)
{
MessageBox.Show(e.Message);
}
// release the underlying COM object
finally
{
if (WMIObject != null)
WMIObject.Dispose();
}
This sample takes all the new property values from an ItemCollection list
view. It first binds to the class instance and then loops through the list
view, gets the latest values and updates the object property values with it.
It then calls ManagementObject.Put() to commit the changes back to the WMI
store (or the provider for dynamic classes). At the end it again calls
Dispose() to free up the underlying COM object.
The "WMI Power Browser" sample application
This sample application demonstrates how to enumerate the list of WMI
namespaces and all classes for each. It also demonstrates how to perform
queries, whether querying for all the classes of a namespace, all class
instances of a class, all associated classes and class instances, all
association classes or class instances, etc. It also allows you to enter a
WQL query string and execute the query, showing its result set. This makes
it easy to experiment with WQL and see right away the result set. The sample
also demonstrates how to create new classes, edit or delete new classes,
clone class instances and edit or delete class instances. It also allows you
to
view all the methods available on a class instance and then to invoke them.
The rich Windows form interface (using tree views, list views, etc.) makes
it very easy to discover all namespaces, classes and class instances. At the
same time you can step through the code and understand in all details how to
work with WMI namespaces, classes and class instances.
The "WMI System Browser" sample application
This sample application shows how you can use WMI to find out a wealth of
information about any machine on your network. The "WMI System Browser"
shows only a subset of information available through WMI. In the list on the
left side you can select a category, for example "processor information",
"BIOS information", "IIS web sites", etc. The list in the middle then shows
you
all found instances, for example it shows each found processor, each
found IIS web site, etc. Selecting a specific instance shows the details in
the list on
the right side, for example the maximum and current clock speed
of a processor, the serial number and release date of the BIOS, etc. Click
on the "Show class description" button to launch the MSDN help page of the
underlying WMI class. This helps a lot to understand all the properties
available on a class. If the class exposes methods then you can click on the
"Invoke a method" button. It lists for you on the left side all available
methods. Selecting a method shows you in the list in the middle all in
parameters. You can set the values for each by clicking on it. Click on the
"Invoke method" button to invoke the method and show the out parameters in
the list on the right side. Click on the "Show method description" to launch
the MSDN help page for the selected method. This helps to understand the in
and out parameters of each method.
It is very simple to extend the sample application and really understand
what information WMI can provide. This could even be a simple
troubleshooting tool, helping the user understand the hardware and software
configuration
of a remote machine. Much of the information shown here can be useful when
resolving issues. Here is a list of information you can view, and which WMI
class is used for it:
- IIsWebServiceSetting the IIS itself. This includes ASP settings, logging settings, authentication
settings, etc.
- IIsApplicationPoolSetting application pools (IIS 6.x specific). For each you can see the user
credentials
used, application pool recycling policy information, etc.
- IIsWebServerSetting
- IIsWebVirtualDirSetting
- Win32_Battery
- Win32_BIOS
- Win32_ComputerSystem owner, the hardware model and manufacturer, etc.
- Win32_ComputerSystemProduct hardware system including the serial number, name, vendor, version,
etc.
- Win32_DiskPartition
Returns the created disk partitions (a
disk partition may span multiple physical disks and contain multiple logical
disks). For each you find the size, block size, number of blocks, etc.
- Win32_Environment each you can see the name, value, if it is a system or user defined
variable, etc.
- Win32_Group - Lists all the Windows groups of your local machine
as well as of the domain, if connected to a domain. For each group you can
see the name, domain, SID, etc.
- Win32_LogicalDisk system and for each the file system, the driver letter, the size and free
space, etc.
- Win32_NetworkAdapterConfiguration configurations (not the physical network adapters). For each network adapter
configuration you see the IP address, subnet mask, is it DHCP enabled,
etc.
- Win32_NTDomain (your local computer is returned as a domain too) and information like the
name, domain controller address, etc.
- Win32_OperatingSystem like build number, serial number, name, OS type, etc.
- Win32_OSRecoveryConfiguration configuration including where to create the dump file, whether to auto
reboot, etc.
- Win32_PageFile including the file path, the file size, etc.
- Win32_PhysicalMemory system and for each the capacity, speed, data width, etc.
- Win32_PnPDevice
- Win32_Printer sizes supported, etc.
- Win32_Process
- Win32_Thread For each thread it shows the total execution time and kernel time in
milliseconds, its priority and the process it belongs to, etc.
- Win32_TimeZone your OS. Returns its name, daylight savings information, etc.
- Win32_UserAccount machine as well as of the domain, if connected to a domain. For each user
you can see the name, domain, SID, etc.
- Win32_Volume capacity, the free size, the file system, etc.
This list already provides a wealth of information about a machine. There
are many more classes available and you can use the "WMI Power Browser" to
discover them and each class instance.
Summary
WMI provides a powerful way to instrument your application and to
allow management tools like Microsoft Operations Manager 2005 to tie into
your application and proactively notify IT operators. But WMI also provides
a wealth of information about systems which you can leverage in your
enterprise applications. It is also very easy to collect that information
for remote machines or to invoke actions on class instances remotely. The
.NET framework provides a comprehensive and easy approach to working with
WMI. It makes it very easy to create WMI classes, expose dynamic WMI class
instances, raise WMI events as well as discover and interact with existing
WMI classes and class instances.
Hopefully future versions of the .NET framework will remove the existing
limitations, the biggest being that you cannot implement methods, cannot
provide write access to properties and only provide dynamic classes. WMI is
still vastly underutilized in today's enterprise applications. Hopefully
these two articles provide a better insight into how to use WMI and the
power
of WMI. If you have comments on this article, please contact me @ klaus_salchner@hotmail.com. I
want to hear if you learned something new. Contact me if you have questions
about this topic or article.
Download Code.zip
About the author
Klaus Salchner has worked for 14 years in the industry, nine years in Europe
and another five years in North America. As a Senior Enterprise Architect
with solid experience in enterprise software development, Klaus spends
considerable time on performance, scalability, availability,
maintainability, globalization/localization and security. The projects he
has been involved in are used by more than a million users in 50 countries
on three continents.
Klaus calls Vancouver, British Columbia his home at the moment. His next
big
goal is doing the New York marathon in 2005. Klaus is interested in guest
speaking opportunities or as an author for .NET magazines or Web sites. He
can be contacted at
klaus_salchner@hotmail.com or http://www.enterprise-minds.com
.
Enterprise application architecture and design consulting services are
available. If you want to hear more about it contact me! Involve me in your
projects and I will make a difference for you. Contact me if you have an
idea for an article or
research project. Also conact me if you want to co-author an article or
join future
research projects!
|