Performing Queries using the System.Management Classes

Now we've seen a bit of how WMI works internally, we are ready to look in more detail at how to code up clients using WMI. The rest of this chapter is devoted to three examples, which respectively illustrate querying the available classes, performing asynchronous queries, and receiving event notifications. In this section we'll cover synchronous operations.

The two core classes that you will be using all the time with WMI are ManagementObject and ManagementClass.ManagementObject represents any WMI object - in other words an instance or a class - while ManagementClass represents only those WMI objects that are classes. ManagementClass derives from ManagementObject, and implements a couple of extra methods to perform tasks such as obtaining all the instances of a class, and obtaining related classes and base classes. In turn, ManagementObject is derived from the class ManagementBaseObject, which implements certain other methods and properties.

We have already seen the use of ManagementClass.GetInstances() to retrieve all instances. This method actually returns a ManagementObjectCollection reference.

 ManagementClass modemClass = new      ManagementClass(@"\\.\root\CIMV2\Win32_POTSModem"); ManagementObjectCollection modems = modems.GetInstances(); foreach (ManagementObject modem in modems) { 

If you need more control over the actual query sent to the object manager, you can use the ManagementObjectSearcher class, in particular the Get() method, as we've already seen. This method also returns a ManagementObjectCollection reference.

Once you have an object, the properties can be returned via an indexer, as we've seen in earlier examples:

 ManagementObject cDrive = new ManagementObject(                                  "Win32_LogicalDisk.DeviceID=\"C:\""); Console.WriteLine(cDrive["VolumeName"]); 

If you want a finer degree of control over the properties, you can use the PropertyData class, which represents an individual property, and features methods to obtain the name, value, qualifiers and other data about a property, such as whether it is an array.

You can obtain a collection of all the available properties on an object using the ManagementObject.Properties property. This technique is also useful if you don't know the names of the properties you will need at compile time:

 ManagementObject cDrive = new ManagementObject(                                  "Win32_LogicalDisk.DeviceID=\"C:\""); PropertyDataCollection props = cDrive.Properties; foreach (PropertyData prop in props) {    // Note that in real production code we'd check for null values here    Console.WriteLine("Name: {0}, Value: {1}", prop.Name,                      prop.Value.ToString()); } 

Note that the PropertyData.Value property returns an object reference - since the actual data type will vary between properties. Hence in the above code snippet we explicitly convert it to a string.

WMI Browser Example

We will now present an example that illustrates the use of the System.Management classes to perform synchronous queries. The WMIBrowser example is just what its name suggests: it's an application that lets you browse around the various namespaces in WMI, and examine classes, instances and properties. This means that the example has an added benefit: if you download and run it, you can use it to get a good feel for the kind of things you can do on your computer with WMI.

When you run the example, you are presented with a form containing a large treeview and associated list box. The treeview shows the complete tree of namespaces. The list box is initially empty, but whenever any node in the treeview is selected, the list box is populated with the names of all the classes in that namespace:

click to expand

This screenshot shows the situation on Windows XP Professional. Which classes you see will depend heavily on your operating system, because of the way that WMI is rapidly evolving. On Windows 2000, for example, you will see fewer namespaces and classes because that was an earlier operating system and so came with fewer WMI providers. Among the namespaces you can see are the CIMV2 one, which contains the classes defined in the CIM V2 Schema and Win32 Extended Schema. Other namespaces include NetFrameworkv1, which contains the classes that can be used to configure .NET, and directory/LDAP which allows access to Active Directory configuration.

The form contains a button captioned Class Details. If you click on this button, a new form will be displayed, containing more details of the class that was selected in the list box. If we click on the button in the above screenshot we get the following:

click to expand

This screenshot shows information about the Win32_LogicalDisk class. In particular, it shows the current instances of the class, as well as the properties and methods defined for the class. For properties the dialog shows the values of each property where available (though for array properties it simply displays <array>). Of course, none of the properties in this screenshot has any values, because we are examining a class, not an instance. There are a few WMI classes that have some property values defined statically for the class as a whole, but Win32_LogicalDisk isn't one of them.

In order to see some property values, we need to select an instance. If we click on any instance of the class shown in the upper-left list box, then the Properties listview changes to show the properties of that instance rather than the whole class:

click to expand

Now we see that a number of properties have appeared, although there are still a fair number of properties that are either apparently not implemented in Windows or do not currently have any values assigned to them.

That's as far as the example goes: the example doesn't allow you to modify the values of properties or to call methods, but it's enough to give you a fair idea of what's in the WMI namespaces.

We'll start by examining the code for the first form. The controls on this form are respectively named tvNamespaces, lbClasses, btnDetails and statusBar. When using the Design View to add these controls, I set the list box to Sorted.

The Form1 constructor invokes a method called AddNamespacesToList(), which is the method that populates the treeview:

 public Form1() {    InitializeComponent();    this.AddNamespacesToList(); } 

This is the code for AddNamespacesToList():

 private void AddNamespacesToList() {    try    {       string nsName = @"\\.\root";       TreeNode node = new TreeNode(nsName);       node.Tag = nsName;       this.tvNamespaces.Nodes.Add(node);       RecurseAddSubNamespaces(nsName, node);       node.Expand();    }    catch (Exception e)    {       this.statusValue.Text = "ERROR: " + e.Message;    } } 

It's traditional in books to neglect error-handling code in examples because such code usually detracts from the purpose of the example. However, when we start dealing with running through all the WMI namespaces and classes, there is a high risk of encountering exceptions, normally because some WMI provider doesn't implement certain features. Because of this risk, most of the substantial methods in this example contain try...catch blocks just to make sure that if an error does occur, an appropriate message is displayed and execution continues. In this case, if any exception is thrown while listing all the namespaces, this will be indicated by a message in the status bar.

As far as the code in the try block is concerned, we start off by placing a root node in the treeview, which represents the root namespace on the current machine. We set the Tag property of the node to this string too. In this example, the nodes under the root node will only display the name of the namespace represented, but if we are to retrieve details of classes in a namespace, then we will need to access the full path of the namespace - we solve this issue here by storing that path in the Tag property of each TreeNode object.

Adding the nodes under the root node is the responsibility of a method called RecurseAddSubNamespaces(). After calling this method, we expand the root node to make the form look prettier (and save the user a mouse click) when the program starts up.

RecurseAddSubNamespaces() looks like this:

 void RecurseAddSubNamespaces(string nsName, TreeNode parent) {    ManagementClass nsClass = new ManagementClass(nsName + ":__namespace");    foreach(ManagementObject ns in nsClass.GetInstances())    {       string childName = ns["Name"].ToString();       TreeNode childNode = new TreeNode(childName);       string fullPath = nsName + @"\" + childName;       childNode.Tag = fullpath;       parent.Nodes.Add(childNode);       RecurseAddSubNamespaces(fullPath, childNode);    } } 

Essentially, this method takes a namespace and its corresponding TreeNode as a parameter. It finds all the namespaces that are directly contained in this parent namespace, and adds them all to the parent node. Then the method recursively calls itself again for each of the newly added children, thus making sure that any children of those namespaces get added, and so on until the entire tree of namespaces has been covered.

The real problem is how we find the child namespaces of a namespace - there is no method in any of the System.Management classes that can directly do this. Instead, the procedure that WBEM has defined for this is to go through the special class called __namespace that I mentioned earlier. The WBEM standard requires that this class should exist in every namespace, and every namespace should contain one instance of this class for each subnamespace. Thus, by enumerating the instances of this class, we can find the names of the child namespaces - which are available via the Name property of each instance of the __namespace class:

 ManagementClass nsClass = new ManagementClass(nsName + ":__namespace"); foreach(ManagementObject ns in nsClass.GetInstances()) 

Now for the code that will be executed when the user selects an item in the treeview:

 private void tvNamespaces_AfterSelect(object sender,                 System.Windows.Forms.TreeViewEventArgs e) {    AddClasses(e.Node.Tag.ToString()); } void AddClasses(string ns) {    lbClasses.Items.Clear();    int count = 0;    try    {       ManagementObjectSearcher searcher = new ManagementObjectSearcher(                                           ns, "select * from meta_class");       foreach (ManagementClass wmiClass in searcher.Get())       {          this.lbClasses.Items.Add(wmiClass["__class"].ToString());          count++;       }       this.statusValue.Text = count + " classes found in selected namespace.";    }    catch (Exception ex)    {       this.statusValue.Text = ex.Message;    } } 

The AddClasses() method uses a WQL query string to obtain all the classes contained in the selected namespace. In order to retrieve the name of each class, it uses the __class property. This is one of those system properties defined automatically for all WBEM classes. __class always contains the name of the class. Finally, the method updates the status bar with the number of classes in the namespace.

The next action to be considered is the handler for when the user clicks the Show Details button:

 private void btnDetails_Click(object sender, System.EventArgs e) {    string classPath = this.tvNamespaces.SelectedNode.Tag.ToString() + ":" +                                     this.lbClasses.SelectedItem.ToString();    ClassAnalyzerForm subForm = new ClassAnalyzerForm(classPath);    subForm.ShowDialog(); } 

The handler first figures out the full path to the selected class, by combining the class name with the namespace path obtained from the node in the treeview. Then we instantiate the second form (which I've called ClassAnalyzerForm) and show it as a modal dialog. The controls I added to this form are called lbInstances (the list box that lists the class instances), lvProperties (the listview that gives the property values) and lbMethods(the small list box that lists the methods of a class). In addition, the two column headers of lbInstances are respectively called hdrName and hdrValue.

The ClassAnalyzerForm class has several extra member fields that I added by hand:

 public class ClassAnalyzerForm : System.Windows.Forms.Form {    ManagementObject[] instances;    ManagementClass mgmtClass;    string className;    private System.Windows.Forms.ListBox lbInstances;    private System.Windows.Forms.Listview lvProperties;    private System.Windows.Forms.ColumnHeader hdrName;    private System.Windows.Forms.ColumnHeader hdrValue;    private System.Windows.Forms.ListBox lbMethods; 

className stores the full path of the class, mgmtClass is a reference to the ManagementClass object that represents this class, while instances is an array of ManagementObject references that represent all the available instances of the class.

There is quite a lot to be done when the form is instantiated:

 public ClassAnalyzerForm(string className) {    InitializeComponent();    this.className = className;    this.Text = this.className;    mgmtClass = new ManagementClass(this.className);    mgmtClass.Get();    AddInstances();    AddProperties(mgmtClass);    AddMethods(); } 

Besides initializing the member fields, we call the ManagementObject.Get() method to retrieve data from the underlying object to be managed. We then call methods to populate the list boxes that will list the instances and methods of the object, and the listview that will display its properties. The reason that the AddProperties() method takes a parameter is that in principle we also want to display the properties of instances, if the user clicks on an instance. Hence it needs to be passed details of precisely which WMI object to use. By contrast, AddInstances() and AddMethods() are never invoked after the form has been instantiated, and always populate their respective listboxes based on the mgmtClass field.

Let's examine AddInstances() first. The instances can be obtained using the ManagementClass.GetInstances() method. The main complicating factor is the need to store all the ManagementObject instance references - we'll need them later on if the user clicks on one of them. Unfortunately, although ManagementObjectCollection has a Count property, this isn't at present implemented, which makes it impossible to tell up front how big an array to allocate to store the instances in. The problem is solved in this method by holding the data in a temporary ArrayList, which is transferred to a proper array when we know how many instances we have:

 public void AddInstances() {    try    {       ManagementObjectCollection instances = mgmtClass.GetInstances();       ArrayList tempInstances = new ArrayList();       ArrayList tempIndices = new ArrayList();       foreach (ManagementObject instance in instances)       {          int index = this.lbInstances.Items.Add(                                        instance["__RelPath"].ToString());          tempInstances.Add(instance);          tempIndices.Add(index);       }       this.instances = new ManagementObject [tempInstances.Count];       for (int i=0; i<tempInstances.Count; i++)          this.instances[(int)tempIndices[i]] = (ManagementObject)                                                         tempInstances[i];    }    catch (Exception e)    {       this.lbInstances.Items.Add("ERROR:" + e.Message);    } } 

The tempIndices ArrayList is used to store the indices at which each ManagementObject is added to the list box. We need this information so that when the user clicks on an item in the list box, we can work out which ManagementObject corresponds to that index. We use the __RelPath property, another system property implemented by all objects, to retrieve the name of the object (strictly speaking, its relative object path).

The process of adding methods to the appropriate list box (called lbMethods) is far simpler since there is no need to store any information:

 void AddMethods() {    try    {       foreach (MethodData instance in mgmtClass.Methods)       {          this.lbMethods.Items.Add(instance.Name);          }    }    catch (Exception e)    {       this.lbMethods.Items.Add("ERROR:" + e.Message);    } } 

Adding properties is complicated by the need to retrieve the property value of an object. In this code we work on the basis that if a property is an array (indicated by the PropertyData.IsArray property), then we just display the string <Array>. If for any reason we can't retrieve the property value (retrieving it either returns null if no value is present, or throws an exception if there is a problem in the provider) then we display the string <No Value>. The individual properties are represented by PropertyData instances:

 void AddProperties(ManagementObject mgmtObj) {    lvProperties.Items.Clear();    try    {       foreach (PropertyData instance in mgmtObj.Properties)       {          ListViewItem prop = new ListViewItem(instance.Name);          if (instance.IsArray)             prop.SubItems.Add("<Array>");          else          {             object value =instance.Value;             if (value == null)                prop.SubItems.Add("<No value>");             else                prop.SubItems.Add(value.ToString());          }          this.lvProperties.Items.Add(prop);       }    }    catch (Exception e)    {       this.lvProperties.Items.Add("ERROR:" + e.Message);    } } 

Finally, we need the event handler that invokes AddProperties() if the user clicks on an instance in the lbInstances list box:

 private void lbInstances_SelectedIndexChanged(object sender,                                               System.EventArgs e) {    AddProperties(instances[lbInstances.SelectedIndex]); } 

And that completes the example.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net