Nathan Ziehnert

10 minute read

WMI is awesome. So much good data is contained in such an easily accessible “database”. Hard drive information, software installs, printer information, services, man oh man there is so much data available to you. Sometimes however, you just have to have more.

Mr. Creosote - Monty Python - Meaning of Life

If you’re interested in the guts (poor Mr. Creosote) instead of the details, you can skip ahead by clicking here

This was the case when I needed to build a ConfigMgr report/query that would get the current status of the TPM (including IsEnabled, IsActivated, and IsOwned). When the Win32_Tpm class is instantiated (or created) it grabs the initial value and dumps the values into IsEnabled_InitialValue, IsActivated_InitialValue, IsOwned_InitialValue as properties. They never change even if the value of those settings changes on the TPM. There are methods available to query the current status, but for good reason ConfigMgr doesn’t collect the return values from a WMI class instance method.

Whoa, hold up… Instance? Method? Huh?

Let’s back up a bit.

WMI Classes

If you’re intimately familiar with WMI you can skip this section. But it’s a quick read.

WMI consists of a collection of Namespaces. Consider a namespace like a database on a SQL server. Inside of those namespaces are things called Classes. You can consider classes like tables in a database. Now each class has Instances or maybe just a single Instance. An instance is like a row in that table. Instances have Properties and Methods. Properties are like the columns in that database table, where each column has some sort of purpose (maybe it’s the computer name or the last time the row was updated). Each instance of the class has the opportunity to put different values in the respective columns.

So far we have a structure that looks something like this:

  • Namespace (Database)
    • Class (Table)
      • Instance (Row)
        • Properties (Columns)

Now here is where we (sorta) diverge from the SQL server analogy. Methods are somewhat like SQL stored procedures, but they are attached directly to the Class, whereas stored procedures in SQL are stored separately and are not tied to a table directly. Methods run some sort of routine against the instance (this could be updating the instance, removing the instance, or even something “unrelated” to the instance like joining a computer to a domain). Methods always return a code (success or failure, like an exit code), and methods sometimes return other data.

Okay, so I think I get it, and I think I understand why ConfigMgr wouldn’t want to scan methods, but can you explain?

ConfigMgr Hardware Inventories and WMI Methods

The ConfigMgr Hardware Inventory scans against a known set of WMI classes and returns the instances to tables in the ConfigMgr database with rows associated with the resource ID of the device. This is configurable in your client settings and you can tick or untick any of the hardware classes that you want to scan against. Just remember that everything you scan from the machine eventually ends up in your database, and there are limits to the amount of data that can be transferred during a hardware inventory. (Basically I’m telling you to not just go and check every box. That would be a bad idea).

If you’re to look at these classes in your client settings, you’ll notice that none of the methods show up - just the properties. The reason? If there was a method called “Disjoin Computer from Domain” it wouldn’t be a good idea to collect data from running that method, right? Or a less dramatic reason might be because you don’t want to run a method called “Delete” on data that you’re trying to collect.

Win32_Tpm

Okay, really… if you’re not interested how to structure the MOF file, you can skip to my final code by clicking here

As I stated earlier in this post the only way to get the current state of the TPM enablement, activation, and ownership is via a method on the Win32_Tpm class. It would have been really easy to run a script against the environment and just build a CSV from the data, but I also wanted something that wouldn’t remain static or have to be rerun regularly if I wanted updated data.

Also let’s be honest. I like a challenge.

Hold my beer

So I wanted to find a way to run a script that would grab the values from those methods and then store them in a separate WMI class that I could consume later in ConfigMgr through a query or a report.

Creating a WMI Class

A MOF file contains the definition of a WMI class. If you’re familiar with C# some of it will look a little familiar (if you’re not, don’t worry - I’ve got you covered). For each definition of a new class you need two top level items: the namespace where the class will be stored, and the name of the class. They look like this:

#pragma namespace ("\\\\.\\root\\cimv2") // define where the class will be stored
Class Win32_TPMExtended                  // name of the class
{
};

Notice three important things:

  1. Backslashes are doubled because a backslash in a MOF file is an escape character (or a character that has a different value based on the character that follows it).
  2. Comments are made with two forward slashes (anything on that line that follows the double forward slash is ignored by the MOF compiler).
  3. The class name has open and closed curly braces following it, followed by a semi-colon. We will define properties inside the curly braces, and the semi-colon tells the compiler that we’re done with the class definition.

Now inside of the class definition, we will define the properties of the class.

Class Win32_TPMExtended
{
    [key]String KeyName;
    Boolean IsEnabled;
    Boolean IsActivated;
    Boolean IsOwned;
};

Each property has a datatype and a name (e.g. String KeyName). A full list of valid datatypes (String, Boolean, etc.) is available here. If you are going to have multiple instances, you will also need a [key]. We probably don’t need one in this case since we’re only creating one instance, but unfortunately I’m far too lazy too far in now to change it.

At this point you’ve created a class (or at least defined it - we haven’t compiled it yet). Now we’ll create a default instance for our class.

Instance of Win32_TPMExtended
{
    KeyName = "TPMExtendedStatus";
};

Notice that we only need to define the KeyName here. We don’t actually have to give any values to the other properties. We’ll set those later using the script. At this point you could compile the MOF file and the class would be created as well as the default instance of that class.

Creating a Class Which Runs a Script

Now that we have the class where we’re going to store data, we need to have a way to populate that data. We’re going to get into a special namespace in WMI called “Subscription”. The subscription namespace which resides at “root\subscription” has a few standard consumer classes that define actions to take when a particular event happens in WMI (e.g. when an instance property is modified, you could fire off a script). The beauty of the “ActiveScriptEventConsumer” class is that you can embed a script in the instance itself. Meaning you don’t have to store the script as a file located somewhere else on the machine. Further meaning that when we modify the Configuration.mof file for ConfigMgr, we can deliver the script directly to the workstation at the time the machine policy is downloaded.

Keep with me here - I’m basically telling you that you can store a script on a computer without there being a physical file on the machine. And that script can be executed anytime a particular WMI event takes place. This requires that we define three instances:

  1. An instance of “ActiveScriptEventConsumer” which will contain the script we want to run
  2. An instance of “__EventFilter” which defines the event that will trigger our script
  3. An instance of “__FilterToConsumerBinding” which binds the event filter and the script together

Creating the ActiveScriptEventConsumer Instance

Similarly to creating a class, we need to define where we’re working (the namespace) and then we need to create an instance.

#pragma namespace ("\\\\.\\root\\subscription")     // create a subscription to run a script on an event
instance of ActiveScriptEventConsumer as $Cons      // in our case, we'll write a VBScript to gather information
{                                                   // from the TPM that is only available via WMI method
    Name = "TPMQuery";
    ScriptingEngine = "VBScript";

    ScriptText = 
        "dim i\n"
        ...
        "End If\n";
        
    CreatorSID = {1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0}; // Administrators SID
};

We’ve added a new word to the instance this time however: “as”. All this does is stores the instance in a variable which we’ll use later. Don’t worry about it too much at this point.

Inside of ActiveScriptEventConsumer we need to define four properties:

  1. Name - the name of ActiveScriptEventConsumer instance
  2. ScriptingEngine - the name of the scripting engine you wish to use. You can use anything that is available to the system, but for compatibility reasons I lean towards VBScript
  3. ScriptText - the actual script that you want to run
  4. CreatorSID - the name of the account that is “creating” the instance. Just use the administrator SID from the above example

I’ve truncated the ScriptText for this post, but notice that each line is encapsulated in quotes, and at the end of a line you see “\n” finally followed by a semi-colon. Remember when we talked about “escape characters”? “\n” represents a new line. If any of the lines in your script contain double quotes, those will have to be escaped as well (").

Creating the __EventFilter Instance

Since we’re still working in the “root\subscription” namespace we don’t have to use the #pragma namespace line again.

instance of __EventFilter as $Filt                  // define the event that triggers the script to run. In our
{                                                   // case, we'll rely on the Hardware Inventory being triggered.
    Name = "TPMQueryEF";
    
    Query = "SELECT * FROM __InstanceModificationEvent WITHIN 5 "
    "WHERE TargetInstance ISA \"InventoryActionStatus\" "
    "AND TargetInstance.InventoryActionID = \""
    "{00000000-0000-0000-0000-000000000001}\"";

    QueryLanguage = "WQL";
    EventNamespace = "\\root\\ccm\\InvAgt";
    CreatorSID = {1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0}; // Administrators SID
};

Here we’re defining five properties:

  1. Name - the name of the __EventFilter instance
  2. Query - the WQL query that we’ll run (which can be split across multiple lines, just as long as it ends with a semi-colon)
  3. QueryLanguage - this will most assuredly always be WQL
  4. EventNamespace - since the event class that we’re watching comes from a different namespace, we need to tell WMI which namespace it’s in
  5. CreatorSID - the name of the account that is “creating” the instance. Just use the administrator SID from the above example

Since this post is focused on ConfigMgr, just steal this instance example. This particular query will run every time the ConfigMgr hardware inventory is run on a machine.

Actually…

Okay, so it actually runs twice. Once at the beginning and once at the end. The reason for this is the “InventoryActionStatus” updates twice on a hardware inventory. But since we’re collecting data for consumption from the Hardware inventory, this is a good event to trigger on.

Creating the __FilterToConsumerBinding Instance

This is the easiest of the instances to create. We literally just bind the event and script together. Remember how we stored the instances in variables earlier? Now we’re going to reference them.

instance of __FilterToConsumerBinding               // bind the event to the script
{
    Filter = $Filt;
    Consumer = $Cons;
    DeliverSynchronously=FALSE;
    CreatorSID = {1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0}; // Administrators SID
};

Four properties are defined this time:

  1. Filter - the instance of the filter that we’re binding.
  2. Consumer - the instance of the ActiveScriptEventConsumer that we’re binding to the filter.
  3. DeliverSynchronously - wait for the script to complete
  4. CreatorSID - the name of the account that is “creating” the instance. Just use the administrator SID from the above example

Provided you used the “as $Filt” and “as $Cons” variables, this shouldn’t ever change from the example above.

Putting it All Together

Okay, so we can put all of this data together into a single MOF file. You can test your MOF file by running MOFCOMP.exe on your machine and point it to the MOF file that you’ve created.

mofcomp.exe c:\path\to\file.mof

This will create the class, the active script consumer, the filter, and the binding. Now you can cause the event to trigger (in our case, run a ConfigMgr hardware inventory) and whatever script you defined will execute (twice.)

[Here’s the “Configuration.mof” file that I created for Win32_TPMExtended.] (/files/2018/07/23/Configuration.mof)

In the next post we’ll create the SMS_def.mof file to import our custom class into the ConfigMgr database and modify the Configuration.mof file to deliver our new classes to our workstations. Until next time, Happy Admining!

comments powered by Disqus

Table of Contents