# Understanding CDXML

CDXML turns WMI queries and method calls into simple and reusable cmdlets. Let's uncover how this PowerShell magic works!

WMI is extremely powerful and provides you with a wealth of information about computer systems and software. Unnoticed by many, the PowerShell team has introduced an exciting technology called Cmdlet Definition XML (CDXML) in PowerShell 3. It can auto-magically turn WMI classes into full-featured PowerShell cmdlets.

## Quick Intro

WMI is much older than PowerShell and has always been an excellent and fairly easy-to-use source of information. When PowerShell initially surfaced, the team did two things to leverage the assets in WMI:

• Simple WMI Access: the cmdlet Get-WmiObject was added to simplify querying WMI for information, and up to today this single cmdlet is still among the most popular and widely used cmdlets.

• Wrapping WMI Queries: some cmdlets were added that internally just wrapped WMI queries in an effort to make things even easier and more discoverable. For example, these two calls produce identical results:

# get hotfix information:
Get-WmiObject -Class Win32_QuickFixEngineering
# same:
Get-Hotfix


Then, in PowerShell 3, the team did a lot of re-thinking:

• Better Standardization: Get-WmiObject was hard-coded to use the old DCOM network protocol. With the advent of WsMan and other remoting standards, it became important to query and access WMI in a flexible yet standardized manner. So the PowerShell team introduced CIM Cmdlets (read more).
• Automatic Cmdlet Generation: Of course cmdlets like Get-Hotfix helped not having to remember awkward class names like Win32_QuickFixEngineering, but manually producing cmdlets for every single WMI class was no option. Instead, the PowerShell team added Cmdlet Definition XML (CDXML) to wrap WMI classes and produce cmdlets in an automated way.

CDXML enables you to auto-magically turn WMI classes and WMI methods into easy-to-use cmdlets, and you’re probably working with CDXML-generated cmdlets all the time without even knowing. CDXML is the reason why we have seen such a steep increase in cmdlets shipping with the Windows operating system.

In this article, I’d like to take you on a tour to discover CDXML. You’ll see how it can tremendously simplify your work with WMI, and you’ll find a lot of example code, including how to enable and disable your webcam (so you don’t accidentally leave your webcam on when the video conference is over).

## Discovering CIM Cmdlets

A normal PowerShell module ships the code for new commands. A PowerShell module based on CDXML in contrast does not contain source code. Instead, it uses a .cdxml file to describe how cmdlets can query WMI classes and call WMI methods.

### Finding CDXML-Based Modules

To find PowerShell modules based on CDXML, search for files with extension .cdxml in one of your module locations:

# find all CDXML-based PowerShell modules:
$env:PSModulePath -split ';' | Get-ChildItem -Filter *.cdxml -Recurse | Select-Object -ExpandProperty DirectoryName | Sort-Object -Unique  On a typical Windows 10 machine, the result looks similar to this and exposes 50 PowerShell modules that in reality just wrap WMI calls: C:\Windows\system32\WindowsPowerShell\v1.0\Modules\AppBackgroundTask C:\Windows\system32\WindowsPowerShell\v1.0\Modules\BranchCache C:\Windows\system32\WindowsPowerShell\v1.0\Modules\ConfigDefender C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Defender C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DFSN\MSFT_DFSNamespace C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DFSN\MSFT_DFSNamespaceAccess C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DFSN\MSFT_DFSNamespaceFolder C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DFSN\MSFT_DFSNamespaceFolderTarget C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DFSN\MSFT_DFSNamespaceRootTarget C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DFSN\MSFT_DFSNamespaceServerConfig C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DhcpServer C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DirectAccessClientComponents C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DnsClient C:\Windows\system32\WindowsPowerShell\v1.0\Modules\DnsServer C:\Windows\system32\WindowsPowerShell\v1.0\Modules\EventTracingManagement C:\Windows\system32\WindowsPowerShell\v1.0\Modules\FailoverClusters C:\Windows\system32\WindowsPowerShell\v1.0\Modules\HgsClient C:\Windows\system32\WindowsPowerShell\v1.0\Modules\IpamServer C:\Windows\system32\WindowsPowerShell\v1.0\Modules\iSCSI C:\Windows\system32\WindowsPowerShell\v1.0\Modules\MMAgent C:\Windows\system32\WindowsPowerShell\v1.0\Modules\MsDtc C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetAdapter C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetConnection C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetEventPacketCapture C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetLbfo C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetLldpAgent C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetNat C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetQos C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetSecurity C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetSwitchTeam C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetTCPIP C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetworkConnectivityStatus C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetworkController C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NetworkTransition C:\Windows\system32\WindowsPowerShell\v1.0\Modules\NFS C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PcsvDevice C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PnpDevice C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PrintManagement C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration C:\Windows\system32\WindowsPowerShell\v1.0\Modules\RemoteAccess C:\Windows\system32\WindowsPowerShell\v1.0\Modules\ScheduledTasks C:\Windows\system32\WindowsPowerShell\v1.0\Modules\ServerManagerTasks C:\Windows\system32\WindowsPowerShell\v1.0\Modules\SmbShare C:\Windows\system32\WindowsPowerShell\v1.0\Modules\SmbWitness C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Storage C:\Windows\system32\WindowsPowerShell\v1.0\Modules\StorageQoS C:\Windows\system32\WindowsPowerShell\v1.0\Modules\StorageReplica C:\Windows\system32\WindowsPowerShell\v1.0\Modules\VpnClient C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Wdac C:\Windows\system32\WindowsPowerShell\v1.0\Modules\WindowsUpdateProvider  Don’t worry about the v1.0 subfolder. The PowerShell team planned on having different PowerShell versions side-by-side in version-specific subfolders and added the folder v1.0. This idea was abandoned, but the folder remained. All versions of Windows PowerShell reside in the subfolder v1.0. ### Understanding .CDXML Files At the core of each of these modules, you find one or more .cdxml files. These xml files use this structure: <?xml version="1.0" encoding="utf-8"?> <PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11"> <Class ClassName="namespace/classname" ClassVersion="1.0.0"> <Version>1.0.0</Version> <DefaultNoun>NounName</DefaultNoun> <InstanceCmdlets> <Cmdlet> <!-- cmdlet definition(s) applying to WMI instances --> </Cmdlet> </InstanceCmdlets> <StaticCmdlets> <Cmdlet> <!-- cmdlet definition(s) applying to WMI classes --> </Cmdlet> </StaticCmdlets> </Class> </PowerShellMetadata>  • Class: the attribute ClassName specifies the WMI namespace and WMI class name that it targets. • DefaultNoun: defines the noun name for the cmdlets that are produced by this file • InstanceCmdlets: defines the cmdlets that target the instances of the WMI class. If the WMI class was Win32_Share, for example, representing network shares, then the cmdlets defined in this section would apply to each individual share that exists. • StaticCmdlets: defines the cmdlets that target the WMI class itself. Many WMI classes provide methods that are independent of actual instances. ## Discovering “Managed” WMI Classes In a next step, let’s read the .cdxml files of all PowerShell modules to discover the names of the WMI classes they manage: $env:PSModulePath -split ';' | Get-ChildItem -Filter *.cdxml -Recurse | ForEach-Object -begin {
# create a xml object to manage the xml content of files:
$xml = [xml]::new() } -process { # load the .cdxml file content into xml object$xml.Load($_.FullName) # create custom object with the WMI class information # if this is not a reference to a CmdletAdapter if ($xml.PowerShellMetadata.Class.CmdletAdapter -eq $null) { # [PSCustomObject]@{ Namespace =$xml.PowerShellMetadata.Class.ClassName | Split-Path
ClassName = $xml.PowerShellMetadata.Class.ClassName | Split-Path -Leaf Module =$_.DirectoryName | Split-Path -Leaf
Path = ($_.FullName -replace 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0', '$pshome')
}
}
} |
# eliminate duplicates
Sort-Object -Property Namespace, ClassName -Unique


When you run this code, it produces a long list of WMI classes. All of these classes are “managed”:

Namespace                                        ClassName                                               Module                        Path
---------                                        ---------                                               ------                        ----
ROOT\cimv2                                       Win32_PnPEntity                                         PnpDevice                     $pshome\Modules\PnpDevice\PnpDevice.cdxml ROOT\microsoft\ipam MSFT_IPAM_AccessScope IpamServer$pshome\Modules\IpamServer\IpamAccessScope.cdxml
ROOT\microsoft\ipam                              MSFT_IPAM_Address                                       IpamServer                    $pshome\Modules\IpamServer\IpamAddress.cdxml ROOT\microsoft\ipam MSFT_IPAM_AddressSpace IpamServer$pshome\Modules\IpamServer\IpamAddressSpace.cdxml
ROOT\microsoft\ipam                              MSFT_IPAM_Block                                         IpamServer                    $pshome\Modules\IpamServer\IpamBlock.cdxml ... ROOT\StandardCimv2 MSFT_PrinterNfcTagTasks PrintManagement$pshome\Modules\PrintManagement\MSFT_PrinterNfcTagTas
ks_v1.0.cdxml
ROOT\StandardCimv2                               MSFT_PrinterPort                                        PrintManagement               $pshome\Modules\PrintManagement\MSFT_PrinterPort_v1.0 .cdxml ROOT\StandardCimv2 MSFT_PrinterPortTasks PrintManagement$pshome\Modules\PrintManagement\MSFT_PrinterPortTasks
_v1.0.cdxml
ROOT\StandardCimv2                               MSFT_PrinterProperty                                    PrintManagement               $pshome\Modules\PrintManagement\MSFT_PrinterProperty_ v1.0.cdxml ROOT\StandardCimv2 MSFT_PrintJob PrintManagement$pshome\Modules\PrintManagement\MSFT_PrintJob_v1.0.cd
xml
ROOT\StandardCimv2                               MSFT_TcpIpPrinterPort                                   PrintManagement               $pshome\Modules\PrintManagement\MSFT_TcpIpPrinterPort _v1.0.cdxml ROOT\StandardCimv2 MSFT_WsdPrinterPort PrintManagement$pshome\Modules\PrintManagement\MSFT_WsdPrinterPort_v
1.0.cdxml


### Using “Managed” WMI Classes

While you can still use the raw native WMI cmdlets like Get-CimInstance, it is much easier to use the managed cmdlets provided by the CDXML-based PowerShell modules.

#### Raw WMI Access…

For example, to get a list of Plug&Play devices, you may have figured out that these are represented by the WMI class Win32_PnPEntity:

Get-CimInstance -ClassName Win32_PnpEntity


This works well, and you can use WMI queries to further refine your search, and for example find all of your webcams:

Get-CimInstance -ClassName Win32_PnpEntity -Filter 'Name LIKE "%camera%"' | Out-GridView -Title 'Select Camera Device'


Obviously, this line of code yields results only if there is at least one camera device present in your system.

If you wanted to actually disable your webcam, things would become a lot more complex. You’d now need to know that the class Win32_PnpEntity has methods called Enable() and Disable(), and you’d need to know how to invoke these methods.

You’d also need to know that the methods Enable() and Disable() have been added in Windows 10 and Server 2016 and aren’t available in older versions of Windows, and that you need to have Administrator privileges to invoke them.

With all of this knowledge, you could come up with a script that can disable webcams:

# disable webcams
Get-CimInstance -ClassName Win32_PnpEntity -Filter 'Name LIKE "%camera%"' |
Out-GridView -Title 'Select Camera Device To Disable' -OutputMode Single |
Invoke-CimMethod -MethodName Disable


### …Versus Managed CDXML Cmdlets

Fortunately, the WMI class Win32_PnPEntity has been turned into managed cmdlets by the PowerShell module PnPDevice as you have discovered earlier:

Get-Command -Module PnPDevice


The module comes with four auto-generated cmdlets:

CommandType Name                  Version Source
----------- ----                  ------- ------
Function    Disable-PnpDevice     1.0.0.0 PnPDevice
Function    Enable-PnpDevice      1.0.0.0 PnPDevice
Function    Get-PnpDevice         1.0.0.0 PnPDevice
Function    Get-PnpDeviceProperty 1.0.0.0 PnPDevice


These four cmdlets represent querying Win32_PnPEntity and calling the three methods the class instances support. So now your code becomes much easier:

# disable webcams:
Get-PnpDevice -FriendlyName *Camera* |
Out-GridView -Title 'Select Camera Device To Disable' -OutputMode Single |
Disable-PnpDevice


When you run this code, you’ll discover even more goodness:

• Better Formatting: the gridview displays the Plug&Play devices in a much more readable format: you just see the four important properties Status (which shows Error when the device is not operational, i.e. because you disabled it), Class, FriendlyName, and InstanceId. When you ran Get-CimInstance, PowerShell showed all properties, and it was much harder and involved more scrolling to identify the right camera in the gridview.
• Better Security: since disabling a device can be a harmful action, PowerShell automatically pops up a confirmation dialog when you call Disable-PnPDevice.
• Silent Output: while calling WMI methods via Invoke-CimMethod always returns information, Disable-PnPDevice by default just disables the device and does not return anything. If you want to know the results, which of course can be useful, add the parameter -PassThru.

To not show the confirmation box and return method results, run this instead:

# disable webcams:
Get-PnpDevice -FriendlyName *Camera* |
Out-GridView -Title 'Select Camera Device To Disable' -OutputMode Single |
Disable-PnpDevice -Confirm:$false -Passthru  ### Identifying Managed WMI Classes You’ve just seen how much easier it is to use the CDXML-based managed cmdlets over the raw WMI access. If you regularly work with WMI classes directly, you might want to check whether the classes you typically use are available via managed cmdlets as well. # listing managed WMI classes and the names of CDXML-based cmdlets:$env:PSModulePath -split ';' | Get-ChildItem -Filter *.cdxml -Recurse | ForEach-Object -begin {
# create a xml object to manage the xml content of files:
$xml = [xml]::new() } -process { # load the .cdxml file content into xml object$xml.Load($_.FullName) # create custom object with the WMI class information # if this is not a reference to a CmdletAdapter if ($xml.PowerShellMetadata.Class.CmdletAdapter -eq $null) { #$moduleName = $_.DirectoryName | Split-Path -Leaf$className  = $xml.PowerShellMetadata.Class.ClassName | Split-Path -Leaf$namespace  = $xml.PowerShellMetadata.Class.ClassName | Split-Path [PSCustomObject]@{ Cmdlet = ('Get-{0}' -f$xml.PowerShellMetadata.Class.DefaultNoun)
Method = ''
ClassName = $className Module =$moduleName
Namespace = $namespace } # add instance method calls:$xml.PowerShellMetadata.Class.InstanceCmdlets.Cmdlet |
Where-Object { $_ } | ForEach-Object { [PSCustomObject]@{ Cmdlet = ('{1}-{0}' -f$xml.PowerShellMetadata.Class.DefaultNoun, $_.CmdletMetadata.Verb) Method = ('{0}()' -f ($_.Method.MethodName -replace '^cim:'))
ClassName = $className Module =$moduleName
Namespace = $namespace } } } } | # eliminate duplicates Sort-Object -Property ClassName, Method  Now you can look up the WMI classes that provide managed cmdlets: Cmdlet Method ClassName Module Namespace ------ ------ --------- ------ --------- Get-ClusterHealth MSCluster_ClusterHealthService FailoverClusters root\MSCLUSTER Get-ClusterHealth GetFault() MSCluster_ClusterHealthService FailoverClusters root\MSCLUSTER Get-ClusterHealth GetMetric() MSCluster_ClusterHealthService FailoverClusters root\MSCLUSTER Get-ClusterNode MSCluster_ClusterService FailoverClusters root\MSCLUSTER Get-ClusterFaultDomain MSCluster_FaultDomain FailoverClusters root\MSCLUSTER Remove-ClusterFaultDomain RemoveFaultDomain() MSCluster_FaultDomain FailoverClusters root\MSCLUSTER Set-ClusterFaultDomain SetFaultDomain() MSCluster_FaultDomain FailoverClusters root\MSCLUSTER Get-ClusterGroupSet MSCluster_GroupSet FailoverClusters root\MSCLUSTER Add-ClusterGroupSet AddGroupToSet() MSCluster_GroupSet FailoverClusters root\MSCLUSTER Add-ClusterGroupSet AddSetProvider() MSCluster_GroupSet FailoverClusters root\MSCLUSTER Remove-ClusterGroupSet Remove() MSCluster_GroupSet FailoverClusters root\MSCLUSTER Remove-ClusterGroupSet RemoveGroupFromSet() MSCluster_GroupSet FailoverClusters root\MSCLUSTER Remove-ClusterGroupSet RemoveSetProvider() MSCluster_GroupSet FailoverClusters root\MSCLUSTER Set-ClusterGroupSet SetSet() MSCluster_GroupSet FailoverClusters root\MSCLUSTER Get-ClusterStorageNode MSCluster_StorageNode FailoverClusters root\MSCLUSTER Remove-ClusterStorageNode RemoveStorageNode() MSCluster_StorageNode FailoverClusters root\MSCLUSTER Set-ClusterStorageNode SetStorageNode() MSCluster_StorageNode FailoverClusters root\MSCLUSTER Get-ClusterStorageSpacesDirect MSCluster_StorageSpacesDirect FailoverClusters root\MSCLUSTER Get-Printer MSFT_3DPrinter PrintManagement ROOT\StandardCimv2 Get-AutologgerConfig MSFT_AutologgerConfig EventTracingManagement ROOT\Microsoft\Windows\Ev entTracingManagement Remove-AutologgerConfig DeleteInstance() MSFT_AutologgerConfig EventTracingManagement ROOT\Microsoft\Windows\Ev entTracingManagement Update-AutologgerConfig ModifyInstance() MSFT_AutologgerConfig EventTracingManagement ROOT\Microsoft\Windows\Ev entTracingManagement ... Get-PnpDevice Win32_PnPEntity PnpDevice ROOT\cimv2 Disable-PnpDevice Disable() Win32_PnPEntity PnpDevice ROOT\cimv2 Enable-PnpDevice Enable() Win32_PnPEntity PnpDevice ROOT\cimv2 Get-PnpDevice GetDeviceProperties() Win32_PnPEntity PnpDevice ROOT\cimv2  As you see, most CDXML-based modules come with their own new WMI classes. Win32_PnPEntity in fact is the only “classic” WMI class that has been turned into a managed module. ## Example: Enabling And Disabling Hardware Thanks to the simple-to-use CDXML-based cmdlets with their filter parameters and the better formatting, you can now easily create two PowerShell functions: one to enable cameras (or any other device you query), and one to disable: #requires -Version 3.0 -Modules PnPDevice #requires -RunAsAdministrator function Enable-Camera { # find disabled cameras: # (change the search phrase to find and manage any other hardware)$result = Get-PnpDevice -FriendlyName *Camera* -Status ERROR -ErrorAction Ignore |
Out-GridView -Title 'Select Camera Device To Enable' -OutputMode Single |
Enable-PnpDevice -Confirm:$false -Passthru if ($result.RebootNeeded)
{
Write-Warning 'Changes require a reboot to take effect.'
}
}

function Disable-Camera
{
# find enabled cameras:
# (change the search phrase to find and manage any other hardware)
$result = Get-PnpDevice -FriendlyName *Camera* -Status OK -ErrorAction Ignore | Out-GridView -Title 'Select Camera Device To Disable' -OutputMode Single | Enable-PnpDevice -Confirm:$false -Passthru

if ($result.RebootNeeded) { Write-Warning 'Changes require a reboot to take effect.' } }  ### Dependencies The script requires the cmdlets Get-PnpDevice, Enable-PnPDevice and Disable-PnPDevice provided by the module PnpDevice. It also requires Administrator privileges. That’s what the #requires statements are for: they ensure that these prerequisites are met. Else, PowerShell won’t run the script. The module PnPDevice is listed as a separate dependency. Isn’t this module part of PowerShell 3? No, it isn’t, and it’s important to understand this: PowerShell 3 (and better) ship with the Cim Cmdlets and CDXML technology. The module PnpDevice however was added to the operating system and introduced in Windows 10 and Server 2016. So when you are running an older Windows version and upgrade to PowerShell 3 or better, you are still missing the module PnpDevice. You’d have to upgrade your operating system, not PowerShell. ### WMI Dependencies In fact, this is the reason why you can’t just copy modules based on CDXML. These modules reference WMI classes and their methods, so to run them, your WMI classes must support this. As it turns out, the WMI instance methods Enable() and Disable() used by Enable-PnPDevice and Disable-PnpDevice where also added only in Windows 10 and Server 2016. If you wanted to enable and disable devices in Windows versions prior to Windows 10 and Server 2016, you’d have to write code yourself to access the SetupAPI directly - or use a PowerShell module that does: Install-Module -Name DeviceManagement -Scope CurrentUser -Force Get-Command -Module DeviceManagement  The free module DeviceManagement comes with methods to enable and disable devices that work independent of WMI and are available in older Windows versions as well: CommandType Name Version Source ----------- ---- ------- ------ Cmdlet Disable-Device 1.3.0 DeviceManagement Cmdlet Enable-Device 1.3.0 DeviceManagement Cmdlet Get-Device 1.3.0 DeviceManagement Cmdlet Get-Driver 1.3.0 DeviceManagement Cmdlet Get-NUMA 1.3.0 DeviceManagement Cmdlet Install-DeviceDriver 1.3.0 DeviceManagement  Beginning with Windows 10 and Server 2016, though, this module is no longer needed as you have seen. ## How Improved Formatting Works Aside from easier discoverability, managed CDXML-based cmdlets also provide much better output. Let’s see how this works, and what your benefits are. ### Raw WMI Output: Hard To Read Open a fresh PowerShell and run this to see what the raw WMI content typically looks like: Get-CimInstance -ClassName Win32_PnPEntity | Select-Object -First 1  This dumps the first available Plug&Play device instance, and you get back a lot of raw WMI information: Caption : HID-compliant system controller Description : HID-compliant system controller InstallDate : Name : HID-compliant system controller Status : OK Availability : ConfigManagerErrorCode : 0 ConfigManagerUserConfig : False CreationClassName : Win32_PnPEntity DeviceID : HID\CONVERTEDDEVICE&COL03\5&7674E02&0&0002 ErrorCleared : ErrorDescription : LastErrorCode : PNPDeviceID : HID\CONVERTEDDEVICE&COL03\5&7674E02&0&0002 PowerManagementCapabilities : PowerManagementSupported : StatusInfo : SystemCreationClassName : Win32_ComputerSystem SystemName : DELL7390 ClassGuid : {745a17a0-74d3-11d0-b6fe-00a0c90f57da} CompatibleID : HardwareID : {HID\ConvertedDevice&Col03, HID\VID_045E&UP:0001_U:0080, HID_DEVICE_SYSTEM_CONTROL, HID_DEVICE_UP:0001_U:0080...} Manufacturer : (Standard system devices) PNPClass : HIDClass Present : True Service : PSComputerName :  This includes properties that use numeric codes, for example ConfigManagerErrorCode. You can only guess that 0 represents “no error”, and if you find other values, it isn’t evident what they might stand for: Get-CimInstance -ClassName Win32_PnPEntity -Filter 'ConfigManagerErrorCode > 0' | Select-Object -Property Name, ConfigManagerErrorCode  In my example, since I just disabled my webcam above, I get this: Name ConfigManagerErrorCode ---- ---------------------- Intel(R) AVStream Camera 22  So with raw WMI content, there are two problems: • Too much: you get back too much information and need to invest time to find the useful properties. • Too cryptic: some of the information is coded and uses numeric values that are hard to understand ### CDXML: Friendly Output When you run this code, you get back a much easier to read representation of a Win32_PnPEntity instance: Get-PnPDevice | Select-Object -First 1  PowerShell now only shows the four most important properties: Status Class FriendlyName InstanceId ------ ----- ------------ ---------- OK HIDClass HID-compliant system controller HID\CONVERTEDDEVICE&COL03\5&7674E02&0&0002  You can still see all the other information by using Select-Object, and when you do, you also see that most numeric values have been replaced by friendly text. This line dumps all Plug&Play devices in error state, and returns the error reason (instead of a code number): If all of your Plug&Play devices work properly, you won’t get back anything. You may want to disable a device to test this. # querying problematic hardware Get-PnpDevice -Status ERROR | Select-Object -Property Name, ConfigManagerErrorCode  The result looks like this: Name ConfigManagerErrorCode ---- ---------------------- Intel(R) AVStream Camera CM_PROB_DISABLED  In fact, CDXML has added a number of additional properties. To get a full status report, try this: Get-PnPDevice | Select-Object -Property Name, Problem, ProblemDescription | Out-GridView  This produces a sophisticated report. Problem is an alias for ConfigManagerErrorCode, yet ProblemDescription is new and provides you with a human-readable description of the problem. Only, ProblemDescription is empty in most cases due to a bug. The module reads the problem descriptions from a local resource file which resides inside the module. Since the resource file uses relative paths, you need to change the current path to the module base folder to see the problem descriptions: # temporarily switch current path to module base Push-Location -Path (Get-Module -Name PnPDevice).ModuleBase # now problem descriptions are shown Get-PnPDevice | Select-Object -Property Name, Problem, ProblemDescription # restore path Pop-Location  Now the report looks good, and ProblemDescription has content: Name Problem ProblemDescription ---- ------- ------------------ HID-compliant system controller CM_PROB_NONE This device is working properly. HID-compliant vendor-defined device CM_PROB_NONE This device is working properly. Jabra BIZ 2300 CM_PROB_PHANTOM Currently, this hardware device is not connected to... Intel(R) Control Logic CM_PROB_NONE This device is working properly. Samsung Flash Drive USB Device CM_PROB_NONE This device is working properly. USB Mass Storage Device CM_PROB_NONE This device is working properly. Bose QC35 II Avrcp Transport CM_PROB_NONE This device is working properly. Killer Networking Software CM_PROB_NONE This device is working properly. Bluetooth LE Generic Attribute Service CM_PROB_NONE This device is working properly. HID-compliant consumer control device CM_PROB_NONE This device is working properly.  ### Formatting Improvements For All Instances Once you have loaded the module PnPDevice, this auto-magically prettifies the output of Win32_PnPEntity instances, regardless of how you produced them. So even raw WMI queries now show friendly texts. The line below, which previously produced a raw numeric ConfigManagerErrorCode, now displays the same friendly result: Get-CimInstance -ClassName Win32_PnPEntity -Filter 'ConfigManagerErrorCode > 0' | Select-Object -Property Name, ConfigManagerErrorCode  Name ConfigManagerErrorCode ---- ---------------------- Intel(R) AVStream Camera CM_PROB_DISABLED  That’s important to understand because this way, you can combine the best of both worlds: Get-PnPDevice is perfect for most routine tasks but it won’t let you do sophisticated server-sided queries. For those, you can still use Get-CimInstance. Just make sure you imported the module to get the sophisticated formatting. Here is an example: Get-PnPDevice supports the parameter -Status so you can actively search for a given status. You cannot, however, negate this and search for any device not in that status. Here comes Get-CimInstance to the rescue: # make sure you import the module to get better formatting Import-Module -Name PnPDevice # use direct WMI cmdlets and still get improved formatting: Get-CimInstance -ClassName Win32_PnPEntity -Filter 'Status <> "OK"' | Select-Object -Property Name, Problem  ## Custom Formats And Types Take a look into the PnPDevice module folder to understand how the module improved the output: explorer "$PSHome\Modules\PnpDevice"


You’ll discover two files: PnPDevice.Format.ps1xml and PnPDevice.Types.ps1xml.

### Formats Prettify WMI Instances

The first file defines the properties that PowerShell displays by default:

notepad "$PSHome\Modules\PnpDevice\PnPDevice.Format.ps1xml"  This format is applied to all objects of these types: <ViewSelectedBy> <TypeName>Microsoft.Management.Infrastructure.CimInstance#ROOT/cimv2/Win32_PnPDeviceProperty</TypeName> <TypeName>Microsoft.Management.Infrastructure.CimInstance#Win32_PnPDeviceProperty</TypeName> </ViewSelectedBy>  That’s why all instances of Win32_PnPDevice appeared prettified as soon as the module PnPDevice was loaded (and added these formats) - regardless of whether you used Get-PnPDevice or Get-CimInstance -ClassName Win32_PnPDevice to produce these instances. ### Type Extensions Prettify Property Content The second file adds new properties to the WMI instances and can turn code numbers into friendly text: notepad "$PSHome\Modules\PnpDevice\PnPDevice.Types.ps1xml"


The type extension applies to all instances of this class which again explains why the benefits apply regardless of how you produced the instances:

<Name>Microsoft.Management.Infrastructure.CimInstance#ROOT/Cimv2/Win32_PnPEntity</Name>


New alias properties like Problem are added like this:

<AliasProperty>
<Name>Problem</Name>
<ReferencedMemberName>ConfigManagerErrorCode</ReferencedMemberName>
</AliasProperty>


### Numeric Values Become Enumerations

Conversions from numerics to friendly text are implemented like this:

<ScriptProperty>
<Name>ConfigManagerErrorCode</Name>
<GetScriptBlock>
[Microsoft.PowerShell.Cmdletization.GeneratedTypes.PnpDevice.Problem]($this.PSBase.CimInstanceProperties['ConfigManagerErrorCode'].Value) </GetScriptBlock> </ScriptProperty>  The raw numeric value is converted into an enumeration type, in this example [Microsoft.PowerShell.Cmdletization.GeneratedTypes.PnpDevice.Problem]. For this to work, the script property needs to read the raw value directly from the CimInstanceProperties, effectively bypassing the PowerShell type system. Else, you’d produce an endless loop. The type [Microsoft.PowerShell.Cmdletization.GeneratedTypes.PnpDevice.Problem] is defined directly in the .cdxml file: notepad "$PSHome\Modules\PnpDevice\PnPDevice.cdxml"


At the end, there is an (optional) <Enums//> section that translates the numeric values to friendly text:

<Enums>
<Enum EnumName="PnpDevice.Problem" UnderlyingType="uint32">
<Value Name="CM_PROB_NONE" Value="0" />
<Value Name="CM_PROB_NOT_CONFIGURED" Value="1" />
...
<Value Name="CM_PROB_USED_BY_DEBUGGER" Value="53" />
<Value Name="CM_PROB_DEVICE_RESET" Value="54" />
<Value Name="CM_PROB_CONSOLE_LOCKED" Value="55" />
<Value Name="CM_PROB_NEED_CLASS_CONFIG" Value="56" />
</Enum>
<Enum EnumName="PnpDeviceProperty.Type" UnderlyingType="uint32">
<Value Name="Empty" Value="0" />
<Value Name="Null" Value="1" />
<Value Name="SByte" Value="2" />
<Value Name="Byte" Value="3" />
...
<Value Name="ErrorArray" Value="4119" />
<Value Name="NTStatusArray" Value="4120" />
<Value Name="StringIndirectList" Value="8217" />
</Enum>
</Enums>


### Buggy: Using Text Resources

Finally, let’s take a look at how the module PnPDevice implemented the new property ProblemDescription, and why this property only works when the current directory is set to the module base.

#### Problem Description

As you have seen, ProblemDescription is always empty unless you set your current folder path to the module base folder:

# temporarily switch current path to module base
Push-Location -Path (Get-Module -Name PnPDevice).ModuleBase
# now problem descriptions are shown
Get-PnPDevice | Select-Object -Property Name, Problem, ProblemDescription
# restore path
Pop-Location


#### Problem Cause

Let’s investigate the cause of this problem. Take a look at the types definition again:

notepad "$PSHome\Modules\PnpDevice\PnPDevice.Types.ps1xml"  ProblemDescription is implemented like this: <ScriptProperty> <Name>ProblemDescription</Name> <GetScriptBlock> Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PnpDevice.Resource.psd1 switch([Microsoft.PowerShell.Cmdletization.GeneratedTypes.PnpDevice.Problem]($this.PSBase.CimInstanceProperties['ConfigManagerErrorCode'].Value))
{
CM_PROB_NONE
{
$str =$LocalizedData.IDS_PROB_NOPROBLEM
}
...


In essence, the problem descriptions are read from a local resource file located in the module folder:

Microsoft.PowerShell.Utility\Import-LocalizedData  LocalizedData -filename PnpDevice.Resource.psd1


Import-LocalizedData reads the content from the .psd1 file into the variable \$LocalizedData. then the remainder of the code produces the problem descriptions.

Unfortunately, all of this code is executed in the caller context, not the module context. So Import-LocalizedData uses the current path to load the file PnpDevice.Resource.psd1 and can only succeed if you happen to have set your current folder to the module base folder.

We have had an interesting discussion on this on Twitter about this with great suggestions from community members. In the end, we came up with a simple fix (see below). Many thanks to all who participated!

#### Fixing Problem

To fix the problem, you need to submit the module root folder to the parameter -BaseDirectory so PowerShell knows where to find the resource file. Fortunately, you can safely assume that the module PnPDevice is already loaded into memory when PowerShell processes the type extension.

So to fix the problem, replace this line in PnpDevice.Types.ps1xml:

Microsoft.PowerShell.Utility\Import-LocalizedData  LocalizedData -filename PnpDevice.Resource.psd1


…with this line:

Microsoft.PowerShell.Utility\Import-LocalizedData  LocalizedData -filename PnpDevice.Resource.psd1 -BaseDirectory (Get-Module -Name PnPDevice).ModuleBase


But wait: since all PowerShell modules shipping with Windows are installed (and protected) by the TrustedInstaller, you don’t have permission to alter the files. So for the time being, the best workaround is to temporarily change the current path as described above using Push-Location and Pop-Location. Hopefully, Microsoft will fix the issue eventually.

If you want to test-drive the fix, simply copy the entire module folder PnPDevice to a place where you do have write permissions, and apply the fix. Next, use Import-Module -Name <path_to_copied_module_folder> to manually load the fixed module into memory.

Now this line works without a flaw regardless of your current location:

Get-PnpDevice | Select-Object -Property Name, Problem* | Out-GridView


I have reported this issue, and hopefully we soon see an official fix. It’s worth it: ProblemDescription provides useful extra-information for your Plug&Play devices.

If you come across PowerShell issues that repro in PowerShell 7, or if you’d like to provide feature requests or other feedback to PowerShell 7, this is the official starting page for your submissions to the PowerShell team.

Before you go ahead, make sure your submission applies to PowerShell 7. There are many issues in Windows PowerShell that have long been fixed in PowerShell 7 (if you haven’t looked at PowerShell 7 yet, you might want to now)