Hey, Scripting Guy! Probing the Depths of WMI

The Microsoft Scripting Guys

Contents

Namespaces
WMI Classes
Properties
Methods

When one of the Scripting Guys was younger, he had two hobbies he really enjoyed. One was drinking fermented beverages; the other was winter camping. He suspects the two were related. A companion of his during those days was fond of suggesting that they "go deep." Well, this month we're going to follow the old friend's advice. We're going deep into the wilderness of WMI (that is, Windows Management Instrumentation).

Thankfully, we don't have to hike anywhere in this case, except perhaps to the coffee maker and back. Instead, we're going to probe the WMI depths using scripts.

Now, the Scripting Guys are well-known for being very practical folk. We try to present solutions to actual problems rather than lofty explanations that seem to leave few details—like how to actually accomplish anything—to the reader. Don't think we're jumping on that bandwagon. Although this column doesn't focus on achieving specific system administration tasks, it does have a practical goal. The primary aim is to educate you about the WMI infrastructure. And we'd also like to put some useful probing scripts in your hands. Fire up Notepad—we're going deep!

Namespaces

The WMI repository is a database, and it's used to store the Common Information Model (CIM). This model is object oriented, which means it consists of a set of descriptions (WMI classes) that represent things WMI can manage. The Win32_Process WMI class, for example, represents processes. The WMI classes are stored in different sections of the WMI repository. A section of the WMI repository is known as a namespace. If you were to stumble upon a WMI repository in the wilderness, the first thing you'd probably notice about it is that it's divided into these high-level namespaces. The script shown in Figure 1

Figure 1 Displaying namespaces

strComputer = "."
Call EnumNameSpaces("root")

Sub EnumNameSpaces(strNameSpace)
    On Error Resume Next
    WScript.Echo strNameSpace
    Set objWMIService=GetObject _
        ("winmgmts:{impersonationLevel=impersonate}\\" & _ 
            strComputer & "\" & strNameSpace)

    Set colNameSpaces = objWMIService.InstancesOf("__NAMESPACE")

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

displays the names of all of the namespaces in the WMI repository on the computer specified in strComputer (a dot means the local computer).

Namespaces can include subname­spaces. You can think of the whole thing as being like a directory structure. So if we were to ask only for all the namespaces (starting with the top-level root namespace), we would get back just the first level of namespaces within root, which would miss any subnamespaces. Instead, we use the trick of recursion. We create a subroutine called EnumNameSpaces that takes a namespace as a parameter and returns all of its subnamespaces.

We kick things off by calling EnumNameSpaces with root as the parameter. This will return all the namespaces within the root namespace. Then we do the recursion. Notice that EnumNameSpaces actually calls itself, passing in each of the subnamespaces it identifies. Take a big slurp of your coffee and think about that. The result is that every namespace will be processed and, if it has subnamespaces, they will all be displayed.

Note that we included an On Error Resume Next statement at the beginning of the subroutine. This is in case you run the script under a security context that doesn't have access to all the namespaces. If that's the case, the script will still run, though it will be a little slow as it waits for timeouts.

Yes, you could just use wbemtest.exe (it's on every computer that has WMI installed) or Scriptomatic (go.microsoft.com/fwlink/?LinkId=125976) to do this. But with a starting script and your elite scripting skills, you can filter those namespaces or output them to Excel or compare the namespaces on two computers.

Now that we can see how the repository is divided up, let's develop a script that allows us to examine what's in each of those sections. We know that WMI classes are stored in each of those namespaces, so let's start by listing them.

WMI Classes

Remember we mentioned that the WMI repository houses the Common Information Model? Well, that model is stored within the CIMV2 (V2 for version 2) namespace. If you look into the CIMV2 namespace, you should see all the WMI classes that make up that model. The following script does that:

strComputer = "."
Set objWMIService=GetObject("winmgmts: _
    {impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in 
    objWMIService.SubclassesOf()
    Wscript.Echo objClass.Path_.Class
Next

Let's see exactly how this script works. The call to GetObject returns an SWbemServices object. GetObject is a VBScript function that returns references to scriptable COM objects. In this case, because we passed it the string "winmgmts:…", GetObject returns an object from the WMI Scripting Library. Notice that the string we pass to GetObject includes the namespace to which we are connecting, \root\cimv2 in this case. So the SWbemServices object we have to work with is bound to the particular namespace we specified. Check out the SWbemServices documentation to see what possible actions you can take in your script.

One that probably looks very familiar to you is ExecQuery. That's the method that lets you run a WQL (Windows Management Instrumentation Query Language) query against the namespace to which you are connected. But there are a bunch of other actions you can take once you have an SWbemServices object associated with a namespace.

We want to see all the WMI classes in the namespace, and SubClassesOf accomplishes that. The documentation says it returns an SWbemObjectSet. That doesn't sound like something you'd want to face alone in the woods! Just focus on the last three letters—it's a set. And, as WMI scripters know, you can walk through a set using For Each.

Now, not too surprisingly, each member of an SWbemObjectSet is an SWbemObject. Each of those SWbem­Objects represents one of the WMI classes in the CIMV2 namespace. Take a look at the documentation for SWbem­Object and you'll see all the information we could output about those classes.

In our script, we've chosen to just display the name of the class. To do that, we accessed the Path_ property. As it turns out, the Path_ property is itself an object. It's an SWbemObjectPath and, being an object, has a bunch of its own properties. We're using Class, which is the name of the class.

So, not only do you have a script that can display all of the WMI classes in a namespace, but you also have a script that can be easily updated to display various other things pertaining those classes. For example, WMI classes can be extensions of other WMI classes. Imagine you have a model for a car (Win32_Car), but you really need to manage station wagons. Everything in the model for the car applies to the station wagon.

But you need a few additional things, such as a Boolean indicating whether it has that fancy wood paneling. You wouldn't want to recreate all of the Win32_Car functionality. What you'd want is a mechanism for extending the Win32_Car class to include all of the new properties. WMI includes just such a mechanism.

To see if a WMI class inherits properties from another WMI class, you can check the Derivation_ property of the SWbemObject class associated with that WMI class. The script in Figure 2 displays the WMI classes in the CIMV2 namespace along with a list of the classes from which they derive.

Figure 2 CIMV2 class derivations

strComputer = "."
Set objWMIService=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in objWMIService.SubclassesOf()
    WScript.StdOut.Write objclass.Path_.Class
    arrDerivativeClasses = objClass.Derivation_ 
    For Each strDerivativeClass in arrDerivativeClasses 
       WScript.StdOut.Write " <- " & strDerivativeClass
    Next
    WScript.StdOut.Write vbNewLine
Next

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

The script starts out just like the previous one. It uses WScript.StdOut.Write instead of WScript.Echo to avoid automatically adding a new line character after the displayed string. (Note: you have to run your script using Cscript.exe instead of Wscript.exe for StdOut.Write to work.)

Looking at the documentation for SWbemObject, you can see it has a Derivation_ property. That property is an array of strings containing the names of classes from which the current class was derived. In our script, we walk through that array using For Each and display all of the classes separated by an ascii arrow. Once you are used to starting with the SWbemServices object returned by GetObject and are walking through the possibilities in the WMI Scripting Library documentation, you can test properties and methods on these oddly named objects to find what's possible.

Understanding which WMI Scripting Library object you are working with at any point in your script lets you move to the next level of WMI scripting. Instead of just using our scripts, you will also understand why we were able to call ExecQuery or reference the Properties_ property. And you'll be able to take things further.

The Scriptomatic tool doesn't live up to your expectations? No problem. Go ahead and modify it to do what you want. Maybe others will purchase your creation from you. In which case, just e-mail instructions to scripter@microsoft.com detailing how we will receive our royalty checks.

Properties

Each WMI class models something you can manage using a set of properties and methods. The properties are characteristics of the thing. A process, for example, has an ID and a priority and uses a certain amount of memory. Those properties are all included in the Win32_Process WMI class.

Once you have identified the class to manage an entity, look at the properties available to see if what you want to manage is in the management model. The SWbemObject class includes a property called Properties_. Funny, huh? The value of that property is an SWbemPropertySet object that includes a collection of SWbemProperty objects. Each of those SWbemProperty objects corresponds to a property in the WMI class associated with the SWbem­Object. I know—all these SWbem* names make everything sound terribly complicated, but it's not all that bad. A Take a look at Figure 3.

fig03.gif

Figure 3 SWbemObject exposes the properties of the WMI class to whichit is bound (Click the image for a larger view)

Keep in mind that the classes that begin with SWbem* are members of the WMI Scripting Object library. They are the objects that let you work with WMI. They are not part of the WMI model of things you can manage.

In Figure 3, SWbemObject represents a WMI class, Win32_SomeClass, which has properties: Property_1, Property_2, and Property_3. It exposes them through its own Properties_ property. Of course, if it's bound to another WMI class, Win32_SomeOther­Class, then the name of its property doesn't change. It's still Properties_. But the properties of the class to which it is bound will likely be different.

Essentially, SWbemObject takes on the properties of the particular WMI class to which it is bound, but it lets you get at those differing properties using the same Properties_ mechanism. Make sense? Take another gulp of coffee and meditate on the diagram. It will all become clear.

The script in Figure 4 leverages SWbemObject and its Properties_ property to retrieve and display all of the properties of the Win32_Service WMI class. The beginning of the script should be familiar. The primary change is that we factored out the namespace and WMI class, making them easier to change. For instance, you can just change the value of strClass to Win32_BIOS to see the properties of that class instead of those of Win32_Service. In the For Each loop, we iterate through the SWbemPropertySet collection (obj­Class.Properties) displaying the Name of each SWbemProperty.

Figure 4 Getting the properties of Win32_Service

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Properties"
WScript.Echo "------------------------------"

For Each objClassProperty in objClass.Properties_
    WScript.Echo objClassProperty.Name
Next

Methods

Finally, some WMI classes go beyond modeling properties or characteristics of a manageable entity and include methods that provide access to behaviors or actions that an entity can take—or have taken against it.

The format of the script to return all the methods of a WMI class (shown in Figure 5) exactly parallels the script that returns properties.

Figure 5 Getting the methods for a WMI class

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Methods"
WScript.Echo "---------------------------"

For Each objClassMethod in objClass.Methods_
    WScript.Echo objClassMethod.Name
Next

The difference, of course is that the Methods_ property is used instead of the Properties_ property. Now, I wonder, would it also be possible to display the types of the parameters these methods take? How might we display only the WMI classes that actually have methods? These are the kinds of questions that you should try to write scripts to answer after you finish reading this column.

Hopefully we've provided you with a useful first glimpse into how to probe the depths of WMI. You do have to make your way through a thick forest of SWbem*s. But scripts provide a nice, lightweight mechanism for going deep. The two winter camping friends weren't so lucky. They never really made it very deep. It turns out an adequate supply of the necessary fermented beverages wasn't very easily transported into the depths in the middle of winter.

The Scripting Guys work for—well, are employed by—Microsoft. When not playing/coaching/watching baseball (and various other activities) they run the TechNet Script Center. Check it out at www.scriptingguys.com.