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:

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:

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.

This is a collection of the type QualifierDataCollection which is a collection of QualifierData types which exposes Name and Value properties to read the name and value of the qualifier.

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:

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:


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:

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:

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.