More CimSession Toolmaking
In the last article, I started demonstrating how you can build your own PowerShell tooling using the CIM .NET classes. While I still recommend using cmdlets like Get-CimInstance
and Get-CimClass
in your functions, advanced scripters may want to build tooling that meets a specific requirement the CIM cmdlets don't address. Today, let's continue to see how we can use the .NET CIM classes to build a custom function that retrieves the CIM-based information.
I'll begin with creating a CimSession
object for the local computer.
$cs =[Microsoft.Management.Infrastructure.CimSession]::Create($env:COMPUTERNAME)
I'm going to focus on getting and enumerating CIM classes and instances.
PS C:\> $cs | Get-Member -MemberType method |
Where Name -match '^((Get)|(Query)|(Enumerate))((Class(es)?)|(Instance(s)?))$' |
Select-Object Name
Name
----
EnumerateClasses
EnumerateInstances
GetClass
GetInstance
QueryInstances
Once you can write code with these methods, then you can branch out into more advanced CIM topics like Associations
.
Classes
CIM classes are organized into namespaces. The default namespace is root\cimv2
so I'll stick to that for the sake of simplicity. To discover class information, you can either enumerate or get a class.
Enumeration
Enumeration is more than just listing classes. Although you can certainly do that.

When you look at the definitions for this method you'll see that you can specify a class name. This might lead you to try a command like:
$cs.EnumerateClasses("ROOT/Cimv2","Win32_Process")
But you'll get no result and no error. In this context, enumeration is looking at the CIM superclass. CIM classes are defined through inheritance which you can see with Get-CimClass
.
PS C:\> Get-CimClass Win32_Process | Select-Object *
CimClassName : Win32_Process
CimSuperClassName : CIM_Process <-----
CimSuperClass : ROOT/CIMV2:CIM_Process
CimClassProperties : {Caption, Description, InstallDate, Name…}
CimClassQualifiers : {Locale, UUID, CreateBy, DeleteBy…}
CimClassMethods : {Create, Terminate, GetOwner, GetOwnerSid…}
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
Enumeration is your way of saying, "Enumerate all the classes derived from this parent class."
PS C:\> $cs.EnumerateClasses("ROOT/Cimv2","CIM_Process")
NameSpace: ROOT/Cimv2
CimClassName CimClassMethods CimClassProperties
------------ --------------- ------------------
Win32_Process {Create, Terminate,… {Caption, Description, InstallDate, Name…}
Here's one more example.
PS C:\> $cs.EnumerateClasses("ROOT/Cimv2","Win32_BaseService")
NameSpace: ROOT/Cimv2
CimClassName CimClassMethods CimClassProperties
------------ --------------- ------------------
Win32_Service {StartService, Stop… {Caption, Description,…
Win32_TerminalService {StartService, Stop… {Caption, Description,…
Win32_SystemDriver {StartService, Stop… {Caption, Description,…
By the way, if all you want to do is enumerate and get class names, you can use the Microsoft.Management.Infrastructure.Options.CimOperationOptions
class.
$opt = [Microsoft.Management.Infrastructure.Options.CimOperationOptions]::new()
$opt.ClassNamesOnly = $True
This is also a little faster.
PS C:\> $cs.EnumerateClasses("ROOT/Cimv2","Win32_BaseService",$opt)
NameSpace: root/cimv2
CimClassName CimClassMethods CimClassProperties
------------ --------------- ------------------
Win32_Service {} {}
Win32_TerminalService {} {}
Win32_SystemDriver {} {}
Now that you understand this method, you might group class names by their prefix.
$cs.EnumerateClasses("ROOT/Cimv2","",$opt) | Group-Object {$_.CimClassName.Split("_")[0]}

Use PowerShell to apply any other filtering.
$cs.EnumerateClasses("ROOT/Cimv2","",$opt).where({$_.CimClassName -notMatch "^__|Win32_Perf"}) |
Sort-Object CimClassName |
Select-Object -ExpandProperty CimClassName
This will filter out the system and the Win32 performance classes.
Getting a Class
On the other hand, the GetClass
method will work as you expect.
PS C:\> $cs.GetClass("root\cimv2","Win32_Process")
NameSpace: root/cimv2
CimClassName CimClassMethods CimClassProperties
------------ --------------- ------------------
Win32_Process {Create, Terminate,… {Caption, Description, InstallDate, Name…}
This should be the same behavior you get with Get-CimClass
. Although, the cmdlet adds a little overhead. The cmdlet took about 34ms whereas the native method took 21ms. Keep in mind that performance isn't always the driving metric.
Instances
Whereas the class is the definition of the thing, an instance is a manifestation of the thing defined by the class. As with classes., we can enumerate or get. We also can filter with a query.
Enumeration
Unlike enumerating classes, you can specify the class name.
PS C:\> $cs.EnumerateInstances("Root/Cimv2","Win32_NetworkAdapter")
DeviceID Name AdapterType ServiceName
-------- ---- ----------- -----------
0 Private Internet Access Network Adapter Ethernet 802.3 tap-pia-0901
1 Microsoft Kernel Debug Network Adapter kdnic
2 Intel(R) Wi-Fi 6 AX201 160MHz Ethernet 802.3 Netwtw10
3 Hyper-V Virtual Switch Extension Adapter Ethernet 802.3 VMSMP
4 Bluetooth Device (Personal Area Network) #2 Ethernet 802.3 BthPan
5 Intel(R) Ethernet Connection (11) I219-LM Ethernet 802.3 e1i68x64
6 Hyper-V Virtual Switch Extension Adapter Ethernet 802.3 VMSMP
7 Hyper-V Virtual Ethernet Adapter #2 Ethernet 802.3 VMSNPXYMP
8 Microsoft Wi-Fi Direct Virtual Adapter Ethernet 802.3 vwifimp
9 WAN Miniport (SSTP) RasSstp
10 WAN Miniport (IKEv2) RasAgileVpn
11 WAN Miniport (L2TP) Rasl2tp
12 WAN Miniport (PPTP) PptpMiniport
13 WAN Miniport (PPPOE) RasPppoe
14 WAN Miniport (IP) Ethernet 802.3 NdisWan
15 WAN Miniport (IPv6) Ethernet 802.3 NdisWan
16 WAN Miniport (Network Monitor) Ethernet 802.3 NdisWan
17 Microsoft Wi-Fi Direct Virtual Adapter #2 Ethernet 802.3 vwifimp
Enumerating an instance will also get all properties. But be careful. Enumeration will give you unexpected results.
PS C:\> $na = $cs.EnumerateInstances("Root/Cimv2","Win32_NetworkAdapter")
PS C:\> $na.count
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
PS C:\>
This is probably more in line with what you had in mind.
PS C:\> $na = $cs.EnumerateInstances("Root/Cimv2","Win32_NetworkAdapter") | Foreach-Object {$_}
PS C:\> $na.count
18

By the way, here's a code snippet that enumerates the top-level namespaces.
PS C:\> $cs.EnumerateInstances("root","__Namespace")
Name PSComputerName
---- --------------
subscription PROSPERO
DEFAULT PROSPERO
CIMV2 PROSPERO
msdtc PROSPERO
Cli PROSPERO
Intel_ME PROSPERO
SECURITY PROSPERO
HyperVCluster PROSPERO
SecurityCenter2 PROSPERO
RSOP PROSPERO
PEH PROSPERO
StandardCimv2 PROSPERO
WMI PROSPERO
MSPS PROSPERO
directory PROSPERO
Policy PROSPERO
Lenovo PROSPERO
virtualization PROSPERO
Interop PROSPERO
Hardware PROSPERO
ServiceModel PROSPERO
SecurityCenter PROSPERO
Microsoft PROSPERO
Appv PROSPERO
Getting an Instance
I think for the most part enumeration is what you will want to do. You can get the instance, assuming you have first enumerated it.
PS C:\> $cs.GetInstance("root/cimv2",$na[0])
DeviceID Name AdapterType ServiceName
-------- ---- ----------- -----------
0 Private Internet Access Network Adapter Ethernet 802.3 tap-pia-0901
I think you only need to get the instance if you are going to modify or invoke a method. For the majority of readers, I think enumerating instances will suffice.
Querying
Back in the VBScript days, we often queried WMI for instances that met particular criteria. The query structure is similar to SQL and we still have the ability today.
PS C:\> Get-CimInstance -query "Select * from win32_process where name = 'pwsh.exe'"
ProcessId Name HandleCount WorkingSetSize VirtualSize
--------- ---- ----------- -------------- -----------
30256 pwsh.exe 1251 529379328 2481666367488
64316 pwsh.exe 1190 491229184 2481512714240
65260 pwsh.exe 1216 513294336 2481217957888
52868 pwsh.exe 963 202440704 2481094983680
We can also use the CimSession
object and its QueryInstance()
method. You need to specify the namespace, the query language, and your query. The query language will always be WQL
.
PS C:\> $cs.QueryInstances("root/cimv2","WQL","Select Name,Handle,WorkingSetSize,ProcessID,CommandLine From Win32_Process Where Name = 'pwsh.exe'")
ProcessId Name HandleCount WorkingSetSize VirtualSize
--------- ---- ----------- -------------- -----------
30256 pwsh.exe 529362944
64316 pwsh.exe 491229184
65260 pwsh.exe 515870720
52868 pwsh.exe 202391552
Was this what you were expecting? PowerShell has a lot of default formatting, including the most common Win32 CIM classes. The query worked as designed. If you re-run this and pipe to Select-Object *
you'll only see values for the specified properties. This is a terrific performance tip.
What I need to do is select the properties I want to see.
$cs.QueryInstances("root/cimv2","WQL","Select Name,Handle,WorkingSetSize,ProcessID,CommandLine From Win32_Process Where Name = 'pwsh.exe'") |
Select-Object Name,Handle,WorkingSetSize,ProcessID,CommandLine

Put it All Together
Let's pull this together into a CIM-based tool. I began this discussion with a sample function that used parameter sets and tried to handle everything. Here's a revised version with a much simpler syntax.
Function Get-OSDetail {
[CmdletBinding()]
[OutputType('OSDetail')]
Param(
[Parameter(
Position = 0,
ValueFromPipeline
)]
[Alias('CN', 'Server')]
[ValidateNotNullOrEmpty()]
[Microsoft.Management.Infrastructure.CimSession[]]$CimSession = $Env:ComputerName
)
Begin {
Write-Verbose "Starting $($MyInvocation.MyCommand)"
#define the WQL query
$Query = 'Select CSName,Caption,Version,BuildNumber,InstallDate,OSArchitecture,RegisteredUser,Organization from Win32_OperatingSystem'
#initialize reference variables
New-Variable -Name ci
New-Variable -Name ce
} #begin
Process {
foreach ($cs in $CimSession) {
#capture connection failures to a variable
if ($cs.TestConnection([ref]$ci, [ref]$ce)) {
Write-Verbose "Querying $($cs.ComputerName.ToUpper())"
$data = $cs.QueryInstances('Root/Cimv2', 'WQL', $query)
[PSCustomObject]@{
PSTypeName = 'OSDetail'
Name = $data.Caption
Version = $data.Version
Build = $data.BuildNumber
OSArchitecture = $data.OSArchitecture
RegisteredUser = $data.RegisteredUser
RegisteredOrganization = $data.Organization
InstallDate = $data.InstallDate
ComputerName = $data.CSName
}
}
else {
Write-Warning "Unable to connect to $($cs.ComputerName.ToUpper()). $($ce.Message)"
}
} #foreach
} #process
End {
Write-Verbose "Ending $($MyInvocation.MyCommand)"
} #end
} #end function Get-OSDetail
The user can specify a computer name or an existing CIM session.
PS C:\> Get-OSDetail
Name : Microsoft Windows 11 Pro
Version : 10.0.22631
Build : 22631
OSArchitecture : 64-bit
RegisteredUser : Jeff Hicks
RegisteredOrganization : JDH IT Solutions, Inc.
InstallDate : 5/12/2022 9:01:53 AM
ComputerName : PROSPERO
This version also demonstrates how to capture the error if the connection test fails.
PS C:\> $r = Get-OSDetail -CimSession dom1,srv3,srv1 -Verbose
VERBOSE: Starting Get-OSDetail
VERBOSE: Querying DOM1
WARNING: Unable to connect to SRV3. WinRM cannot process the request. The following error occurred while using Kerberos
authentication: Cannot find the computer srv3. Verify that the computer exists on the network and that the name
provided is spelled correctly.
VERBOSE: Querying SRV1
VERBOSE: Ending Get-OSDetail
I don't have to worry about alternate credentials. If one is required, the user can create a CIM session and then pipe the sessions to the function.
PS C:\> Get-CimSession | Get-OSDetail -Verbose | Select-Object Computername,Version,Name
VERBOSE: Starting Get-OSDetail
VERBOSE: Querying SRV1
VERBOSE: Querying SRV2
VERBOSE: Querying DOM1
VERBOSE: Ending Get-OSDetail
ComputerName Version Name
------------ ------- ----
SRV1 10.0.17763 Microsoft Windows Server 2019 Standard Evaluation
SRV2 10.0.17763 Microsoft Windows Server 2019 Standard Evaluation
DOM1 10.0.17763 Microsoft Windows Server 2019 Standard Evaluation
Summary
With this knowledge, I can now write CIM-related functions without having to accommodate all use cases. And, my code will run a little faster than the native CIM cmdlets. Again, what I've covered in these two articles is not beginner scripter content. I would encourage you to continue using cmdlets like Get-CimClass
and Get-CimInstance
. But when you are ready for the next level, you can dive into the CIM classes.
Please feel free to leave questions and comments.