The other night when I presented for the Mississippi PowerShell Users' Group, one of the members showed some PowerShell 3.0 code using the CIM cmdlets. At issue is how the CIM cmdlets handle the WMI system properties like __SERVER and __RELPATH. By default, those properties aren't displayed, but they are captured in the CimSystemProperties property.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The problem is that the __PATH property is not captured when using Get-CIMInstance as you can see in the screen shot. This is apparently a known issue. Using Get-WMIObject still works.
PS C:\> get-wmiobject win32_service -filter "name='bits'" | Select -expand __PATH \\SERENITY\root\cimv2:Win32_Service.Name="BITS"
Most of the time this probably isn't a big deal. But perhaps there are situations where you need the __PATH property. I saw some code the other night that constructed the __PATH property. I was intrigued and decided to figure this out for myself. The path needs the computer name, the namespace-class path, the name of the class, the class key and the value of that key. I have most of this already from the
PS C:\> $c.CimSystemProperties.ServerName SERENITY PS C:\> $c.CimSystemProperties.Namespace.Replace("/","\") root\cimv2 PS C:\> $c.CimSystemProperties.ClassName Win32_Service
I switched the direction of the slash in the namespace. The only part I'm missing is the key property. But I can find it by looking at the qualifiers for the class properties. Think of a qualifier as a tag to denotes a special use. What is really cool about the CIMInstance, is that class information is included. This means I can look at the class from the object itself.
PS C:\> $c.cimclass.CimClassProperties | select name,qualifiers
All I need to do is find the property with a KEY qualifier.
PS C:\> $c.cimclass.CimClassProperties | where {$_.qualifiers.name -contains 'key'} Name : Name Value : CimType : String Flags : Property, Key, ReadOnly, NullValue Qualifiers : {read, key} ReferenceClassName :
Now that I know the Name property is the key, I can finish building my path.
PS C:\> $Key = $c[0].CimClass.CimClassProperties | >> where {$_.qualifiers.name -contains "key"} | >> select -ExpandProperty Name >> PS C:\> $c | Select @{Name="__PATH";Expression={ >> '\\{0}\{1}:{2}{3}' -f $_.CimSystemProperties.ServerName, >> $_.CimSystemProperties.Namespace.Replace("/","\"), >> $_.CimSystemProperties.ClassName, >> ".$($key)=""$($_.$key)""" >> }} >> __PATH ------ \\SERENITY\root\cimv2:Win32_Service.Name="BITS"
And this is the exact path I would get using Get-WMIObject.
PS C:\> Get-WmiObject win32_service -filter "name='bits'" | select __Path __PATH ------ \\SERENITY\root\cimv2:Win32_Service.Name="BITS"
Of course, I don't want to have to do all that typing so I created a function to do the work for me.
#requires -version 3.0 Function Add-CIMPath { <# .Synopsis Add the __PATH property to a CIMInstance object .Description The Get-CIMInstance cmdlet by default doesn't display the WMI system properties like __SERVER. The properties are available in the CimSystemProperties property except for __PATH. This function will construct the __PATH property and add it to a CIMInstance object. .Example PS C:\> get-ciminstance win32_memorydevice | add-cimpath | select __Path __PATH ------ \\SERVER01\root\cimv2:Win32_MemoryDevice.DeviceID="Memory Device 0" \\SERVER01\root\cimv2:Win32_MemoryDevice.DeviceID="Memory Device 1" .Example PS C:\> get-ciminstance win32_bios -computer netbk8 | add-cimpath | format-list __Path,PSComputername __PATH : \\NETBK8\root\cimv2:Win32_BIOS.Name="Rev 1.0 ",SoftwareElementID="Rev 1.0 ",SoftwareElementState="3",TargetOperatingSystem="0",Version="LENOVO - 6040000" PSComputerName : netbk8 .Inputs CIMInstance .Outputs CIMInstance .Link Get-CIMInstance #> [cmdletbinding()] Param ( [Parameter(Position=0,ValueFromPipeline=$True)] [ValidateNotNullorEmpty()] [ciminstance]$Inputobject ) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" } #begin Process { #get the key class property Write-Verbose "Processing $($Inputobject.CimClass.CimClassName)" $Key = $Inputobject.CimClass.CimClassProperties | where {$_.qualifiers.name -contains "key"} | select -ExpandProperty Name Write-Verbose "Creating __PATH Using key property $key" $Inputobject | Add-Member -PassThru -MemberType NoteProperty -Name __PATH -Value ( '\\{0}\{1}:{2}{3}' -f $_.CimSystemProperties.ServerName.ToUpper(), $_.CimSystemProperties.Namespace.Replace("/","\"), $_.CimSystemProperties.ClassName, $( if ($key -is [array]) { #create a string with the array of key names and values [string]$s = ".$($key[0])=""$($_.($key[0]))""" #add each additional key separated by comma for ($i=1;$i -lt $key.count;$i++) { $s+= ",$($key[$i])=""$($_.($key[$i]))""" } $s } elseif ($Key) { #just a single key ".$($key)=""$($_.$key)""" } else { #no key '=@' }) ) #value } #process End { Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } #end } #end Add-CIMPath
The main part of the function is essentially what I just demonstrated. However, there are some special cases where there is no key property or there are multiple keys, so I had to add some logic to take that into account. I wrote the function assuming you would pipe a CIMInstance object to it and you want to add __PATH.
PS C:\> get-ciminstance win32_logicaldisk | Add-CIMPath | select __Path __PATH ------ \\SERENITY\root\cimv2:Win32_LogicalDisk.DeviceID="C:" \\SERENITY\root\cimv2:Win32_LogicalDisk.DeviceID="D:" \\SERENITY\root\cimv2:Win32_LogicalDisk.DeviceID="E:" \\SERENITY\root\cimv2:Win32_LogicalDisk.DeviceID="F:" \\SERENITY\root\cimv2:Win32_LogicalDisk.DeviceID="G:" \\SERENITY\root\cimv2:Win32_LogicalDisk.DeviceID="Z:"
And here is what the special cases look like.
I'll be the first to admit this is a bit of a brute force hack and I can't guarantee I've covered every oddball use case. So if you try this and come across a class that doesn't give the correct __PATH, I hope you'll let me know.