Eric's Technical Outlet

Learning the hard way so you don't have to

Use PowerShell to Discover Long-Distance CIM (WMI) Relationships

Usually when you need information from CIM (WMI), you only need to query a single instance. Sometimes, you need to dig through a series of class relationships to find what you want. I’ve built a pair of PowerShell scripts to make that easier.

CIM vs WMI

A quick word on CIM and WMI — CIM stands for “Common Information Model”. It is a standard governed by the Distributed Management Task Force. Microsoft extended CIM when they added it to Windows and branded it “Windows Management Instrumentation”. In the intervening years, they have largely moved away from their original WMI-stamped interfaces in favor of newer CIM-labelled features. The underlying data and functions have not changed.

In Windows PowerShell, you can use cmdlets with *Wmi*, such as Get-WmiObject. These cmdlets did not find their way into PowerShell Core, and never will. The CIM cmdlets, such as Get-CimInstance, exist in both PowerShell offerings.

I have begun using the CIM cmdlets exclusively. They’re faster in most cases and boast many features that you cannot find in the WMI cmdlets, such as separation of System properties from Instance properties and simple ways to detect whether or not a property is a key. The only features I have been unable to locate in the CIM cmdlets is a way to uncover glue classes without exploring the namespace or a way to specify antecedent vs. descendant associators. Those are topics for another article.

Simple CIM Queries

Let’s start with a run-down of this article’s focus. Let’s say that you want to know your system’s serial number so that you can get support from the manufacturer. Just query Win32_Bios:

Get-CimInstance -ClassName Win32_Bios

or, more practically when working interactively:

gcim win32_bios

You’ll get an object with a SerialNumber property that you can (maybe) use:

Sample display of Win32_Bios output

That sort of thing tends to work perfectly well in the default namespace (root/cimv2). Most of the classes have only one instance, so you don’t need to bother with relationships. Many of the other namespaces tell a different tale.

CIM Association Queries

What happens when you have multiple instances of a class, and you seek information related to one of them that is held by another class? As an example, you can find all of your system’s power management settings inside instances of Win32_PowerSetting in root/cimv2/power. But, how do you know if one attaches to the current power plan? Instances of Win32_PowerPlan represent the system’s power plans, so you can ask CIM to tell you how they connect.

So, if you want to only see the settings for the active power plan, start by asking CIM for that plan:

$ActivePowerPlan = Get-CimInstance -Namespace root/cimv2/power -ClassName Win32_PowerPlan -Filter 'IsActive=True'

OK, so the settings are connected to that plan, right? So, would you think that we can just pull associated instances? Let’s try it:

$ActivePowerPlan | Get-CimAssociatedInstance -ResultClassName Win32_PowerSetting

What did you get? You got nothing. Why? Because CIM does not directly attach the settings to their associated plan. Why? Well, I didn’t design the thing, so I can’t tell you that. What now?

How about we first find out what CIM does attach to Win32_PowerPlan?

(Get-CimAssociatedInstance -InputObject $ActivePowerPlan).CimClass.CimClassName

That will give you a long list of Win32_PowerSettingDataIndex instances. Let’s find out what CIM connects to those by cherry-picking the first one:

$idx1 = (Get-CimAssociatedInstance -InputObject $ActivePowerPlan)[0]

Let’s see its associated instances:

(Get-CimAssociatedInstance -InputObject $idx1).CimClass.CimClassName

Now we know the association path. Win32_PowerPlan connects to Win32_PowerSettingDataIndex connects to Win32_PowerSetting. Walking through them looks something like this:

$ActivePowerPlan = Get-CimInstance -Namespace root/cimv2/power -ClassName Win32_PowerPlan -Filter 'IsActive=True'
$ActivePlanSettingIndexes = Get-CimAssociatedInstance -InputObject $ActivePowerPlan -ResultClassName Win32_PowerSettingDataIndex
foreach($ActivePlanSettingIndex in $ActivePlanSettingIndexes)
{
    Get-CimAssociatedInstance -InputObject $ActivePlanSettingIndex -ResultClassName Win32_PowerSetting
}

OK, I suppose that’s not so bad, right? Of course, if you want to step through all of the power plans instead of just the active one, you’d have to telescope out another foreach, but still OK, right?

But wait, we still have the Win32_PowerSettingSubgroup and Win32_PowerSettingDefinition instances that contain useful information. So, more nested foreach’s right?

That’s just in the root/CIMV2/power branch. I typically work in root/virtualization/v2 where I frequently need to walk five or more steps to get what I need. That’s a lot of foreachs. Just looking at the script gives that “Eww, gross!” feeling.

A Tale of Two Scripts

We have two problems: discoverability and code bloat.

Problem 1: Discoverability

The example that I gave above only has us walk through a couple of levels to figure out how everything is connected. It gets really tedious very quickly as you need to traverse more deeply. Without a good breadcrumb system, even a minor distraction can cause you to lose your place in the hierarchy.

The CIM cmdlets do little to help as, even though CIM contains the necessary information, you cannot restrict your association search to only antecedents or only descendants. So, you sometimes find yourself accidentally walking back up a tree. Other times, the tree structure is complicated enough that you must walk the tree in multiple directions.

Problem 2: Code Bloat

Once you finally learn the relationship between your source and destination instance(s), now you need to code the thing up. Sometimes you get “lucky” and the target instance has a property that you can use in a -Filter. But, that’s brittle, and you don’t always get lucky. So, you wind up with a lot of foreachs. And then you discover that you also need to walk a different path using different criteria, so you have a lot of nearly duplicated script.

The Solution, in Two Scripts

I built two scripts to address these problems.

Find-CimAssociatedInstance

Find-CimAssociatedInstance is the workhorse of these two scripts. You give it the source instance that you want to work from and the class name of the associated instance that you want to find, and turn it loose.

You can use the script to do everything, but my primary intent was to overcome the discoverability problem. In its first design (unpublished), I naively walked each association tree until I found what I was looking for or until I hit a brick wall, then I went back and started at the next first-level association. That approach generally yields the fastest results in one-off uses, but has no concern for the overall traversed distance.

So, I rebuilt it to search all first-level instances, then all second-level instances, etc. That approach takes the most time, but results in the shortest possible distance.

The script does take into account that several trees have multiple pathways to the same object, which makes duplicate positives and infinite recursion possible. It protects itself against checking the same object for associations more than once.

Unfortunately, because the CIM cmdlets do not have good support for antecedent vs. descendants distinctions, the script must search radially. That can cause it to loop upward and discover relationships that you don’t want. To combat that, I provided the -ExcludeBranches parameter. Give it a class name or a path, even partials. It will not search any matching instances for associations.

Two Script Modes

The script has two modes. The first returns the discovered instances. The second returns the textual path to those instances. Specify -PathOnly to get the second behavior.

Find-CimAssociatedInstance Demo

To solve our problem indicated above, just run this:

$ActivePowerPlan = Get-CimInstance -Namespace root/cimv2/power -ClassName Win32_PowerPlan -Filter 'IsActive=True'
Find-CimAssociatedInstance.ps1 -InputObject $ActivePowerPlan -ResultClassName Win32_PowerSetting

If you want to see the path instead:

$ActivePowerPlan = Get-CimInstance -Namespace root/cimv2/power -ClassName Win32_PowerPlan -Filter 'IsActive=True'
Find-CimAssociatedInstance.ps1 -InputObject $ActivePowerPlan -ResultClassName Win32_PowerSetting -PathOnly -MaxResults 1

That will output:

Win32_PowerPlan/Win32_PowerSettingDataIndex/Win32_PowerSetting

How to Get Find-CimAssociatedInstance.ps1

I have published this script on my GitHub page: https://github.com/ejsiron/Poshery/blob/master/Standalone/Find-CimAssociatedInstance.ps1. It has full support for Get-Help, including the -Online parameter which takes you to this page: https://github.com/ejsiron/Poshery/blob/master/Docs/Find-CimAssociatedInstance.md. I included many examples to help you get going.


Get-CimDistantInstance

Find-CimAssociatedInstance does a lot of work. For long trees, it can take quite a while. I needed a script to quickly get to what I wanted when I no longer needed the discovery phase. So, I wrote Get-CimDistantInstance. I can interactively use Find-GetCimAssociatedInstance with the -PathOnly parameter. I can use its output in a permanent script file.

So, after discovering that I get to Win32_PowerSetting via the index class, I can write a script like this:

$PowerPlanToSettingPath = 'Win32_PowerSettingDataIndex/Win32_PowerSetting'
$ActivePowerPlan = Get-CimInstance -Namespace root/cimv2/power -ClassName Win32_PowerPlan -Filter 'IsActive=True'
$PlanSettings = Get-CimDistantInstance.ps1 -InputObject $ActivePowerPlan -PathToInstance $PowerPlanToSettingPath

Neat, tidy, and I could search a deeper tree with the exact same number of script lines.

Notice that the path does not contain the name of the source instance, which means that you cannot directly use the output of Find-CimAssociatedInstance. That was a necessary restriction because multiple CIM instances relate to other instances of the same name, and any fool-proofing to prevent unexpected behavior ballooned the script unacceptably. Since I expect you’ll use Find- interactively and save the results permanently, it seemed acceptable to leave it out.

To quickly transform the output of Find-CimAssociatedInstance -PathOnly so that Get-CimDistantInstance can use it:

$SplitPathToInstance = $PathToInstance.Substring($PathToInstance.IndexOf('/') + 1)

That’s a quick and dirty solution; it will error if the input path string is empty.

How to Get Get-CimDistantInstance.ps1

You can also find this script on my GitHub page: https://github.com/ejsiron/Poshery/blob/master/Standalone/Get-CimDistantInstance.ps1. It has online help as well: https://github.com/ejsiron/Poshery/blob/master/Docs/Get-CimDistantInstance.md.

I Can Use Help!

I can only test so much on my own, and I certainly did not think of all possibilities. Make requests and suggestions. Submit code fixes and enhancements, if you like. I prefer to have them on the GitHub pages, but you can use the comments section here, if you wish.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: