Creating CDXML Modules

With CDXML, you can turn WMI classes into a source for powerful new cmdlets.

CDXML (Cmdlet Definition XML) was introduced in PowerShell 3 and auto-magically turns WMI classes into rich and reusable cmdlets. Microsoft has used this technique extensively and added many new WMI classes in custom namespaces, then surfaced them as new PowerShell cmdlets.

View full list of CDXML-based PowerShell modules on Windows 10
Module Name Classes Namespace
AppBackgroundTask ps_backgroundtask r
BranchCache msft_netbranchcacheclientsettingdata, msft_netbranchcachecontentserversettingdata, msft_netbranchcachedatacache, msft_netbranchcachedatacacheextension, msft_netbranchcachehashcache, msft_netbranchcachehostedcacheserversettingdata, msft_netbranchcachenetworksettingdata, msft_netbranchcacheorchestrator, msft_netbranchcachestatus root\standardcimv2
ConfigDefender msft_mpthreat, msft_mpthreatdetection, msft_mpwdoscan root\microsoft\windows\defender
Defender msft_mpcomputerstatus, msft_mppreference, msft_mpscan, msft_mpsignature, msft_mpthreatcatalog root\microsoft\windows\defender
DhcpServer ps_dhcpserver, ps_dhcpserverauditlog, ps_dhcpserverdatabase, ps_dhcpserverdnscredential, ps_dhcpserverindc, ps_dhcpserversecuritygroup, ps_dhcpserversetting, ps_dhcpserverv4binding, ps_dhcpserverv4class, ps_dhcpserverv4dnssetting, ps_dhcpserverv4exclusionrange, ps_dhcpserverv4failover, ps_dhcpserverv4failoverreplication, ps_dhcpserverv4failoverscope, ps_dhcpserverv4filter, ps_dhcpserverv4filterlist, ps_dhcpserverv4freeipaddress, ps_dhcpserverv4iprecord, ps_dhcpserverv4lease, ps_dhcpserverv4multicastexclusionrange, ps_dhcpserverv4multicastlease, ps_dhcpserverv4multicastscope, ps_dhcpserverv4multicastscopestatistics, ps_dhcpserverv4optiondefinition, ps_dhcpserverv4optionvalue, ps_dhcpserverv4policy, ps_dhcpserverv4policyiprange, ps_dhcpserverv4reservation, ps_dhcpserverv4scope, ps_dhcpserverv4scopestatistics, ps_dhcpserverv4statistics, ps_dhcpserverv4superscope, ps_dhcpserverv4superscopestatistics, ps_dhcpserverv6binding, ps_dhcpserverv6class, ps_dhcpserverv6dnssetting, ps_dhcpserverv6exclusionrange, ps_dhcpserverv6freeipaddress, ps_dhcpserverv6lease, ps_dhcpserverv6optiondefinition, ps_dhcpserverv6optionvalue, ps_dhcpserverv6reservation, ps_dhcpserverv6scope, ps_dhcpserverv6scopestatistics, ps_dhcpserverv6statelessstatistics, ps_dhcpserverv6statelessstore, ps_dhcpserverv6statistics, ps_dhcpserverversion root\microsoft\windows\dhcp
DirectAccessClientComponents msft_daclientexperienceconfiguration, msft_dasitetableentry root\standardcimv2
DnsClient ps_dnsclientnrptglobal, ps_dnsclientnrptpolicy, ps_dnsclientnrptrule, msft_dnsclient, msft_dnsclientcache, msft_dnsclientglobalsetting, msft_dnsclientserveraddress root\microsoft\windows\dns
DnsServer ps_dnsserver, ps_dnsservercache, ps_dnsserverclientsubnet, ps_dnsserverconditionalforwarder, ps_dnsserverdiagnostics, ps_dnsserverdirectorypartition, ps_dnsserverdnssecpublickey, ps_dnsserverdnsseczonesetting, ps_dnsserverdssetting, ps_dnsserveredns, ps_dnsserverforwarder, ps_dnsserverglobalnamezone, ps_dnsserverglobalqueryblocklist, ps_dnsserverkeystorageprovider, ps_dnsserverpolicy, ps_dnsserverprimaryzone, ps_dnsserverqueryresolutionpolicy, ps_dnsserverrecursion, ps_dnsserverrecursionscope, ps_dnsserverresourcerecord, ps_dnsserverresourcerecorda, ps_dnsserverresourcerecordaaaa, ps_dnsserverresourcerecordaging, ps_dnsserverresourcerecordcname, ps_dnsserverresourcerecorddnskey, ps_dnsserverresourcerecordds, ps_dnsserverresourcerecordmx, ps_dnsserverresourcerecordptr, ps_dnsserverresponseratelimiting, ps_dnsserverresponseratelimitingexceptionlist, ps_dnsserverroothint, ps_dnsserverscavenging, ps_dnsserversecondaryzone, ps_dnsserversetting, ps_dnsserversigningkey, ps_dnsserversigningkeyrollover, ps_dnsserverstatistics, ps_dnsserverstubzone, ps_dnsservertrustanchor, ps_dnsservertrustpoint, ps_dnsservervirtualizationinstance, ps_dnsserverzone, ps_dnsserverzoneaging, ps_dnsserverzonedelegation, ps_dnsserverzonekeymasterrole, ps_dnsserverzonescope, ps_dnsserverzonesign, ps_dnsserverzonetransfer, ps_dnsserverzonetransferpolicy, ps_dnsserverzoneunsign root\microsoft\windows\dns
EventTracingManagement msft_autologgerconfig, msft_etwtraceprovider, msft_etwtracesession root\microsoft\windows\eventtracingmanagement
FailoverClusters mscluster_clusterhealthservice, mscluster_clusterservice, mscluster_faultdomain, mscluster_groupset, mscluster_storagenode, mscluster_storagespacesdirect root\mscluster
HgsClient msft_hgsclientconfiguration, msft_hgsguardian, msft_hgskeyprotector root\microsoft\windows\hgs
IpamServer msft_ipam_accessscope, msft_ipam_address, msft_ipam_addressspace, msft_ipam_block, msft_ipam_configlogentry, msft_ipam_customfield, msft_ipam_customfieldassociation, msft_ipam_customvalue, msft_ipam_dbconfig, msft_ipam_dhcpconfiglogentry, msft_ipam_dhcpscope, msft_ipam_dhcpserver, msft_ipam_dhcpsuperscope, msft_ipam_discoverydomain, msft_ipam_dnsconditionalforwarder, msft_ipam_dnsresourcerecord, msft_ipam_dnsserver, msft_ipam_dnszone, msft_ipam_gateway, msft_ipam_ipaddressutilizationthreshold, msft_ipam_ipamutilizationdata, msft_ipam_ipauditlogentry, msft_ipam_range, msft_ipam_server, msft_ipam_servercapabilities, msft_ipam_serverconfiguration, msft_ipam_serverinventory, msft_ipam_subnet root\microsoft\ipam
iSCSI msft_iscsiconnection, msft_iscsisession, msft_iscsitarget, msft_iscsitargetportal root\microsoft\windows\storage
MMAgent ps_mmagent r
MsDtc msft_dtcadvancedhostsettingtask, msft_dtcadvancedsettingtask, msft_dtcclusterdefaulttask, msft_dtcclustertmmappingtask, msft_dtcdefaulttask, msft_dtclogtask, msft_dtcnetworksettingtask, msft_dtctask, msft_dtctransactionsstatisticstask, msft_dtctransactionstracesessiontask, msft_dtctransactionstracesettingtask, msft_dtctransactiontask root\msdtc
MSFT_DFSNamespace msft_dfsnamespace r
MSFT_DFSNamespaceAccess msft_dfsnamespaceaccess r
MSFT_DFSNamespaceFolder msft_dfsnamespacefolder r
MSFT_DFSNamespaceFolderTarget msft_dfsnamespacefoldertarget r
MSFT_DFSNamespaceRootTarget msft_dfsnamespaceroottarget r
MSFT_DFSNamespaceServerConfig msft_dfsnamespaceserverconfig r
NetAdapter msft_netadapter, msft_netadapteradvancedpropertysettingdata, msft_netadapterbindingsettingdata, msft_netadapterchecksumoffloadsettingdata, msft_netadapterencapsulatedpackettaskoffloadsettingdata, msft_netadapterhardwareinfosettingdata, msft_netadapteripsecoffloadv2settingdata, msft_netadapterlsosettingdata, msft_netadapterpacketdirectsettingdata, msft_netadapterpowermanagementsettingdata, msft_netadapterqossettingdata, msft_netadapterrdmasettingdata, msft_netadapterrscsettingdata, msft_netadapterrsssettingdata, msft_netadaptersriovsettingdata, msft_netadaptersriovvfsettingdata, msft_netadapterstatisticssettingdata, msft_netadapterusosettingdata, msft_netadaptervmqqueuesettingdata, msft_netadaptervmqsettingdata, msft_netadaptervportsettingdata root\standardcimv2
NetConnection msft_netconnectionprofile r
NetEventPacketCapture msft_neteventnetworkadapter, msft_neteventpacketcaptureprovider, msft_neteventprovider, msft_neteventsession, msft_neteventvfpprovider, msft_neteventvmnetworkadapter, msft_neteventvmswitch, msft_neteventvmswitchprovider, msft_neteventwfpcaptureprovider root\standardcimv2
NetLbfo msft_netlbfoteam, msft_netlbfoteammember, msft_netlbfoteamnic root\standardcimv2
NetLldpAgent msft_netlldpagent r
NetNat msft_netnat, msft_netnatexternaladdress, msft_netnatglobal, msft_netnatsession, msft_netnatstaticmapping root\standardcimv2
NetQos msft_netqospolicysettingdata r
NetSecurity msft_netaddressfilter, msft_netapplicationfilter, msft_netconsecrule, msft_netfirewallprofile, msft_netfirewallrule, msft_netgpo, msft_netikemmcryptoset, msft_netikep1authset, msft_netikep2authset, msft_netikeqmcryptoset, msft_netinterfacefilter, msft_netinterfacetypefilter, msft_netipsecdospsetting, msft_netipsecidentity, msft_netmainmoderule, msft_netmainmodesa, msft_netnetworklayersecurityfilter, msft_netprotocolportfilter, msft_netquickmodesa, msft_netsecdeltacollection, msft_netsecuritysettingdata, msft_netservicefilter root\standardcimv2
NetSwitchTeam msft_netswitchteam, msft_netswitchteammember root\standardcimv2
NetTCPIP msft_netcompartment, msft_netipaddress, msft_netipinterface, msft_netipv4protocol, msft_netipv6protocol, msft_netneighbor, msft_netoffloadglobalsetting, msft_netprefixpolicy, msft_netroute, msft_nettcpconnection, msft_nettcpsetting, msft_nettransportfilter, msft_netudpendpoint, msft_netudpsetting root\standardcimv2
NetworkConnectivityStatus msft_daconnectionstatus, msft_ncsipolicyconfiguration root\standardcimv2
NetworkTransition msft_net6to4configuration, msft_netdnstransitionconfiguration, msft_netdnstransitionmonitoring, msft_netiphttpsconfiguration, msft_netiphttpsstate, msft_netisatapconfiguration, msft_netnattransitionconfiguration, msft_netnattransitionmonitoring, msft_netteredoconfiguration, msft_netteredostate root\standardcimv2
NFS msft_nfsclientconfig, msft_nfsclientgroup, msft_nfsclientlock, msft_nfsmappingstore, msft_nfsmountedclient, msft_nfsnetgroupstore, msft_nfsopenfile, msft_nfsserverconfig, msft_nfsservertasks, msft_nfssession, msft_nfsshare, msft_nfsstatistics root\microsoft\windows\nfs
PcsvDevice msft_pcsvdevice r
PnpDevice win32_pnpentity r
PrintManagement msft_3dprinter, msft_localprinterport, msft_lprprinterport, msft_printer, msft_printerconfiguration, msft_printerdriver, msft_printernfctag, msft_printernfctagtasks, msft_printerport, msft_printerporttasks, msft_printerproperty, msft_printjob, msft_tcpipprinterport, msft_wsdprinterport root\standardcimv2
PSDesiredStateConfiguration msft_dsclocalconfigurationmanager r
RemoteAccess ps_bgpcustomroute, ps_bgppeer, ps_bgproute, ps_bgprouteaggregate, ps_bgprouteflapdampening, ps_bgprouter, ps_bgproutingpolicy, ps_bgproutingpolicyforpeer, ps_bgpstatistics, ps_daappserver, ps_daappserverconnection, ps_daclient, ps_daclientdnsconfiguration, ps_daentrypoint, ps_daentrypointdc, ps_damgmtserver, ps_damultisite, ps_danetworklocationserver, ps_daotpauthentication, ps_daserver, ps_ipfilter, ps_remoteaccess, ps_remoteaccessaccounting, ps_remoteaccessconfiguration, ps_remoteaccessconnectionstatistics, ps_remoteaccessconnectionstatisticssummary, ps_remoteaccesshealth, ps_remoteaccessinboxaccountingstore, ps_remoteaccessloadbalancer, ps_remoteaccessloadbalancernode, ps_remoteaccessradius, ps_remoteaccessroutingdomain, ps_remoteaccessuseractivity, ps_routingprotocolpreference, ps_vpnauthprotocol, ps_vpnauthtype, ps_vpnipaddressassignment, ps_vpnipaddressrange, ps_vpns2sinterface, ps_vpns2sinterfacestatistics, ps_vpnserveripsecconfiguration, ps_vpnsstpproxyrule, ps_vpntrafficselector, ps_vpnuser root\microsoft\windows\remoteaccess
ScheduledTasks msft_scheduledtask, ps_clusteredscheduledtask, ps_scheduledtask root\microsoft\windows\taskscheduler
ServerManagerTasks msft_servermanagertasks r
SmbShare msft_smbbandwidthlimit, msft_smbclientconfiguration, msft_smbclientnetworkinterface, msft_smbconnection, msft_smbglobalmapping, msft_smbmapping, msft_smbmultichannelconnection, msft_smbmultichannelconstraint, msft_smbopenfile, msft_smbserverconfiguration, msft_smbservernetworkinterface, msft_smbsession, msft_smbshare root\microsoft\windows\smb
SmbWitness msft_smbwitnessclient r
Storage msft_disk, msft_diskimage, msft_fileintegrity, msft_fileserver, msft_fileshare, msft_filestoragetier, msft_initiatorid, msft_initiatorport, msft_maskingset, msft_offloaddatatransfersetting, msft_partition, msft_physicaldisk, msft_resiliencysetting, msft_storageenclosure, msft_storagehealth, msft_storagejob, msft_storagenode, msft_storagepool, msft_storageprovider, msft_storagereliabilitycounter, msft_storagesetting, msft_storagesubsystem, msft_storagetier, msft_targetport, msft_targetportal, msft_virtualdisk, msft_volume, ps_storagecmdlets root\microsoft\windows\storage
StorageQoS msft_storageqospolicy, msft_storageqospolicystore, msft_storageqosvolume root\microsoft\windows\storage
StorageReplica msft_wvradmintasks r
VpnClient ps_eapconfiguration, ps_vpnconnection, ps_vpnconnectionipsecconfiguration, ps_vpnconnectionproxy, ps_vpnconnectionroute, ps_vpnconnectiontrigger, ps_vpnconnectiontriggerapplication, ps_vpnconnectiontriggerdnsconfiguration, ps_vpnconnectiontriggertrustednetwork, ps_vpnserveraddress root\microsoft\windows\remoteaccess\client
Wdac msft_odbcdrivertask, msft_odbcdsntask, msft_odbcperfcountertask, msft_wdacbidtracetask root\microsoft\windows\wdac
WindowsUpdateProvider msft_wuoperations, msft_wusettings, msft_wuupdate root\microsoft\windows\windowsupdate

That’s great but ever since Windows NT days, we have had a set of hundreds of classic and powerful WMI classes such as Win32_OperatingSystem or Win32_Process. Zillions of PowerShell scripts rely on these.

So why not create CDXML files for these classic WMI classes as well, and let them surface as simple-to-use PowerShell cmdlets, too? That’s interesting for anyone who has previously used WMI, and it’s really exciting for anyone who hasn’t: because it makes working with WMI so much easier and more discoverable.

In this article, I share with you how you can use CDXML to auto-generate cmdlets from just about any WMI class you wish, and take the WMI class Win32_OperatingSystem as an example.

This is part of a larger project I recently started to provide CDXML definitions for all classic WMI classes.

Raw WMI Queries

WMI provides you with rich information about your computer hard- and software. CDXML makes it much easier to access this information as you’ll see in a second. Before I start looking at CDXML, let’s first refresh the basic WMI know-how.

If you are experienced with WMI, you can safely skip this first section. Or move directly to the final CDXML-based module.

Retrieving Instances

Without CDXML, you can access WMI information from PowerShell via a set of cmdlets, the so-called CIM Cmdlets. In this article, I’ll focus on the WMI class Win32_OperatingSystem which represents your Windows operating system.

To get information about your operating system, you use Get-CimInstance to retrieve all instances of the WMI class Win32_OperatingSystem:

# retrieve all instances of Win32_OperatingSystem and show all properties:
Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property *

With Win32_OperatingSystem, there is always exactly one instance. When you use other WMI classes like Win32_Service or Win32_TapeDrive, there can be more instances (one per service), or none at all (if no tape drive is available).

The result looks similar to this:

Status                                    : OK
Name                                      : Microsoft Windows 10 Pro|C:\Windows|\Device\Harddisk0\Partition3
FreePhysicalMemory                        : 21116160
FreeSpaceInPagingFiles                    : 4858956
FreeVirtualMemory                         : 22062668
Caption                                   : Microsoft Windows 10 Pro
Description                               : Hello
InstallDate                               : 03.09.2019 12:42:41
CreationClassName                         : Win32_OperatingSystem
CSCreationClassName                       : Win32_ComputerSystem
CSName                                    : DELL7390
CurrentTimeZone                           : 120
Distributed                               : False
LastBootUpTime                            : 29.04.2020 21:03:07
LocalDateTime                             : 03.05.2020 11:31:20
MaxNumberOfProcesses                      : 4294967295
MaxProcessMemorySize                      : 137438953344
NumberOfLicensedUsers                     :
NumberOfProcesses                         : 254
NumberOfUsers                             : 2
OSType                                    : 18
OtherTypeDescription                      :
SizeStoredInPagingFiles                   : 4980736
TotalSwapSpaceSize                        :
TotalVirtualMemorySize                    : 38297380
TotalVisibleMemorySize                    : 33316644
Version                                   : 10.0.18363
BootDevice                                : \Device\HarddiskVolume1
BuildNumber                               : 18363
BuildType                                 : Multiprocessor Free
CodeSet                                   : 1252
CountryCode                               : 49
CSDVersion                                :
DataExecutionPrevention_32BitApplications : True
DataExecutionPrevention_Available         : True
DataExecutionPrevention_Drivers           : True
DataExecutionPrevention_SupportPolicy     : 2
Debug                                     : False
EncryptionLevel                           : 256
ForegroundApplicationBoost                : 2
LargeSystemCache                          :
Locale                                    : 0407
Manufacturer                              : Microsoft Corporation
MUILanguages                              : {de-DE, en-US}
OperatingSystemSKU                        : 48
Organization                              : psconf.eu
OSArchitecture                            : 64-bit
OSLanguage                                : 1031
OSProductSuite                            : 256
PAEEnabled                                :
PlusProductID                             :
PlusVersionNumber                         :
PortableOperatingSystem                   : False
Primary                                   : True
ProductType                               : 1
RegisteredUser                            : [email protected]
SerialNumber                              : 00330-50000-00000-AAOEM
ServicePackMajorVersion                   : 0
ServicePackMinorVersion                   : 0
SuiteMask                                 : 272
SystemDevice                              : \Device\HarddiskVolume3
SystemDirectory                           : C:\Windows\system32
SystemDrive                               : C:
WindowsDirectory                          : C:\Windows
PSComputerName                            :
CimClass                                  : root/cimv2:Win32_OperatingSystem
CimInstanceProperties                     : {Caption, Description, InstallDate, Name...}
CimSystemProperties                       : Microsoft.Management.Infrastructure.CimSystemProperties

Selecting Properties

Use Select-Object to retrieve only a subset of properties:

# get path locations for operating system
Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property SystemDrive, WindowsDirectory, SystemDirectory, SystemDevice, BootDevice

This provides you with a list of path names:

SystemDrive      : C:
WindowsDirectory : C:\Windows
SystemDirectory  : C:\Windows\system32
SystemDevice     : \Device\HarddiskVolume3
BootDevice       : \Device\HarddiskVolume1

Querying Specific Property Values

To access the value of a specific property, use dot notation:

# get free space in page file
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$memory = $os.FreeSpaceInPagingFiles
# $memory is in KB, not bytes!
'Paging File Free: {0:n1} GB' -f ($memory*1KB/1GB)

The result looks similar to this:

Paging File Free: 4,6 GB

Analysis: The Good, The Bad, And The Ugly

Retrieving WMI instances directly via Get-CimInstance works well and isn’t too complex. But there are a number of shortcomings with this approach that CDXML aims to fix:

  • Discoverability: you can’t discover WMI classes from within PowerShell (except if you use specific tools). So you need to know that there is a class named Win32_OperatingSystem in the first place. Get-Command won’t expose it to you.

    Once you wrap Win32_OperatingSystem in CDXML definition, there is a new cmdlet named Get-WmiOperatingSystem. No more need to guess the class name.

  • Readability: while many properties returned by WMI are self-explaining, there are a lot of them that are plain numbers and use internal codes or units. For example, run this line:

    Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property FreePhysicalMemory, OSType, DataExecutionPrevention_SupportPolicy, ForegroundApplicationBoost, OperatingSystemSKU, OSLanguage, ProductType, SuiteMask
    

    Would you understand what these numbers want to tell you?

    FreePhysicalMemory                    : 21190940
    OSType                                : 18
    DataExecutionPrevention_SupportPolicy : 2
    ForegroundApplicationBoost            : 2
    OperatingSystemSKU                    : 48
    OSLanguage                            : 1031
    ProductType                           : 1
    SuiteMask                             : 272
    

    Even the apparently obvious FreePhysicalMemory is misleading because it reports the memory in the unit KB, not byte. Once you wrap Win32_OperatingSystem in CDXML definition, the result looks like this:

    FreePhysicalMemory                    : 20,24 GB
    OSType                                : WindowsNT
    DataExecutionPrevention_SupportPolicy : OptIn
    ForegroundApplicationBoost            : Maximum
    OperatingSystemSKU                    : PRODUCTPROFESSIONAL
    OSLanguage                            : de-DE
    ProductType                           : Workstation
    SuiteMask                             : TerminalServices, TerminalServicesSingleSession
    

    This is so much better readable.

Changing Properties

Most properties returned by WMI are read-only but some can also be modified. For example, the property Description can hold an arbitrary description of your operating system.

This description surfaces in the system control panel item where you can change it interactively:

control sysdm.cpl

To automate this, use Set-CimInstance. To change the description of your operating system, run this:

# change operating system description
# (requires admin privileges)
$values = @{
	Description = 'My Computer'
}
Set-CimInstance -Query 'Select * from Win32_OperatingSystem' -Property $values

Set-CimInstance can change one or more properties. Submit a hashtable with the property names and their new values.

Analysis: The Good, The Bad, And The Ugly

Changing properties via Set-CimInstance isn’t especially hard. But you do need a lot of prerequisites and expert knowledge:

  • Information required: you need to know that the WMI class Win32_OperatingSystem controls the operating system description, and that the controlling property is called Description. In fact, you also need to know the names of the properties that can be modified - most of them are read-only.
  • Discoverability: nothing of this is intuitive nor discoverable via Get-Command, so changing the operating system description (or anything else that is changeable via WMI) requires expert knowledge.

Once you wrap Win32_OperatingSystem in CDXML definition, there is a new cmdlet called Set-WmiOperatingSystem that makes changing the operating system description intuitive and simple:

Set-WmiOperatingSystem -Description 'Primary notebook Tobias'

Calling Methods

WMI instances can come with methods (commands), and in fact Win32_OperatingSystem ships with five methods. Use them to change the operating system date and time, and to log off, shutdown, or reboot your system.

Calling methods can be complex because you need to know the method name and all required parameters and parameter types.

For example, to restart your computer after a delay of 30 seconds, and log the restart in the system event log, you can use the method Win32ShutdownTracker like this:

# forcefully restarting computer after 30 seconds
$arguments = @{
	Flags = 6
	Comments = 'Testing automated restart'
	ReasonCode = 1234
	Timeout = 30
}

$query = 'Select * From Win32_OperatingSystem'

Invoke-CimMethod -Query $query -MethodName Win32ShutdownTracker -Arguments $PSBoundParameters 

You’d have to know what the argument Flags means. In the example above, the value 6 really is a bitflag and consists of 2 (Reboot) and 4 (Force):

[Flags()]Enum OperatingSystemFlags
{
  Logoff     = 0        # Logs the user off the computer. Logging off stops all processes associated with the security context of the process that called the exit function, logs the current user off the system, and displays the logon dialog box.
  Shutdown   = 1        # Shuts down the computer to a point where it is safe to turn off the power. (All file buffers are flushed to disk, and all running processes are stopped.) Users see the message *It is now safe to turn off your computer.* During shutdown the system sends a message to each running application. The applications perform any cleanup while processing the message and return True to indicate that they can be terminated.
  Reboot     = 2        # Shuts down and then restarts the computer.
  Force      = 4        # When the flag *Force* is added, all services, including WMI, are shut down immediately. Because of this, you will not be able to receive a return value if you are running the script against a remote computer. When you specify only the flag *Force*, the user will immediately be logged off.
  PowerOff   = 8        # Shuts down the computer and turns off the power (if supported by the computer in question).
}

Analysis: The Good, The Bad, And The Ugly

Calling WMI methods really is where typical PowerShell users run into problems: there often is just too much formal information required to run a method properly.

Once you wrap Win32_OperatingSystem in CDXML definition, there is a new cmdlet called Invoke-WmiOperatingWin32ShutdownTracker that makes calling this method very simple. It even provides argument completion for Flags:

Invoke-WmiOperatingSystemWin32ShutdownTracker -Flags Reboot,Force -Timeout 30 -Comment 'A planned restart' -ReasonCode 1234

Creating CDXML PowerShell Module

Let’s turn the WMI class Win32_OperatingSystem into a PowerShell module now.

The fundamental structure of each CDXML file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">

  <!--referencing the WMI class this cdxml uses-->
  <Class ClassName="root/cimv2/Win32_OperatingSystem" ClassVersion="2.0">
    <Version>1.0</Version>

    <!--default noun used by Get-cmdlets when no other noun is specified-->
    <DefaultNoun>WmiOperatingSystem</DefaultNoun>

    <!--define the cmdlets that work with class instances.-->
    <InstanceCmdlets>
      <!--query parameters to select instances. This is typically empty for classes that provide only one instance-->
      <GetCmdletParameters />
    </InstanceCmdlets>
  </Class>
</PowerShellMetadata>

This structure defines the key information PowerShell needs to automatically create cmdlets:

  • ClassName: defines the WMI namespace and class name that this CDXML definition refers to. In our case, this is root/cimv2/Win32_OperatingSystem.
  • DefaultNoun: defines the default noun part of all cmdlets. This can be anything you choose. I decided to name the default noun WmiOperatingSystem to indicate that this cmdlet is retrieving WMI information from the class Win32_OperatingSystem.
  • InstanceCmdlets: contains the node GetCmdletParameters which can define search parameters to filter instances. Since there is always only one instance of Win32_OperatingSystem, this section is empty.

This XML structure can be expanded in a multitude of ways, but for now the basic structure is sufficient. Let’s start using it!

Implementing Get-WmiOperatingSystem

At a minimum, every CDXML modules contains a cmdlet that retrieves all instances of a WMI class. In this example, it is Get-WmiOperatingSystem. Let’s create the file Win32_OperatingSystem.cdxml:

$testfolder = "$env:temp\cdxmlTest"

$cdxml = @'
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">

  <!--referencing the WMI class this cdxml uses-->
  <Class ClassName="root/cimv2/Win32_OperatingSystem" ClassVersion="2.0">
    <Version>1.0</Version>

    <!--default noun used by Get-cmdlets and when no other noun is specified. By convention, we use the prefix "WMI" and the base name of the WMI class involved. This way, you can easily identify the underlying WMI class.-->
    <DefaultNoun>WmiOperatingSystem</DefaultNoun>

    <!--define the cmdlets that work with class instances.-->
    <InstanceCmdlets>
      <!--query parameters to select instances. This is typically empty for classes that provide only one instance-->
      <GetCmdletParameters />
    </InstanceCmdlets>
  </Class>
</PowerShellMetadata>
'@

# create a test folder if it does not exist
$exists = Test-Path -Path $testfolder -PathType Container
if (!$exists) { $null = New-Item -Path $testfolder -ItemType Directory }

# write cdxml file
$path = Join-Path -Path $testfolder -ChildPath 'win32_operatingsystem.cdxml'
$cdxml | Set-Content -Path $path -Encoding UTF8

Importing PowerShell Module

Each CDXML file really is a PowerShell module so you can use Import-Module to import it:

# import cdxml file as a PowerShell module:
$testfolder = "$env:temp\cdxmlTest"
$path = Join-Path -Path $testfolder -ChildPath 'win32_operatingsystem.cdxml'
Import-Module -Name $path -Verbose -Force

I am adding the parameter -Force to make sure that the module is imported each time you run this code. So when you make changes to the CDXML content, PowerShell updates the module when you import it.

The output looks like this:

VERBOSE: Loading module from path 'C:\Users\tobia\AppData\Local\Temp\cdxmlTest\win32_operatingsystem.cdxml'.
VERBOSE: Importing function 'Get-WmiOperatingSystem'.

The module successfully imported the new cmdlet Get-WmiOperatingSystem, and you can start using it:

# using the cdxml-defined new cmdlet:
Get-WmiOperatingSystem
Get-WmiOperatingSystem | Select-Object -Property *

Implementing Set-WmiOperatingSystem

As you have seen previously, Win32_OperatingSystem contains a number of properties that can be modified. To modify existing properties, add Set-WmiOperatingSystem. This is the CDXML definition:

<Cmdlet>
    <CmdletMetadata Verb="Set" ConfirmImpact="High"/>
    <Method MethodName="cim:ModifyInstance">
        <Parameters>
            <Parameter ParameterName="Description">
                <Type PSType="string" />
                <CmdletParameterMetadata ValueFromPipelineByPropertyName="true">
                    <AllowEmptyString />
                    <ValidateNotNull />
                </CmdletParameterMetadata>
            </Parameter>
            <Parameter ParameterName="ForegroundApplicationBoost">
                <Type PSType="Win32_OperatingSystem.ForegroundApplicationBoost" />
                <CmdletParameterMetadata ValueFromPipelineByPropertyName="true">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                </CmdletParameterMetadata>
            </Parameter>
        </Parameters>
    </Method>
    <GetCmdletParameters />
</Cmdlet>

Let’s see how the xml works:

  • Cmdlet: this node defines one or more additional cmdlets that apply to WMI class instances.

  • CmdletMetadata: defines the cmdlet Verb (in this case Set) and optionally Noun (if you omit the noun attribute, the cmdlet uses the default noun defined earlier). You can also define a ConfirmImpact which describes how “dangerous” the action is. Allowable values are Low, Medium, and High. When you choose High, PowerShell by default prompts the user for confirmation. I chose High because to illustrate the default confirmation later.

  • Method: this defines the instance method the cmdlet should call. For property changes, this is always the internal method cim:ModifyInstance.

  • Parameters: this defines the parameters for your cmdlet.

  • Parameter: each of these defines exactly one parameter. In the case of the method cim:ModifyInstance, the parameter names must be the names of the properties you want to modify. If you want the cmdlet to use different names for the parameters, define an alternate parameter name with the attribute PSName.

  • Type: defines the data type for this parameter. The data type must match the expected data type for the property you want to change. For example, the property Description is of type string, so the parameter that asks for a new value for Description needs to also be of this type.

    You can also define new types. For example, the property ForegroundApplicationBoost really is an unsigned 8-bit integer (byte) which supports the values 0, 1, and 2. So I am assigning a custom type to this parameter: Win32_OperatingSystem.ForegroundApplicationBoost. You can name this type any way you want. You’ll see in a second how this type is defined.

  • CmdletParameterMetadata: defines cmdlet metadata such as pipeline binding and validator attributes, much similar to how you define function parameters.

  • GetCmdletParameters: defines query parameters that can be used to filter applicable instances. Since Win32_OperatingSystem always has exactly one instance, this section can be empty.

Adding Custom Types

I decided to assign a custom type Win32_OperatingSystem.ForegroundApplicationBoost to the parameter ForegroundApplicationBoost. This type needs to be defined as a Enum in a separate Enums section:

<Enums>
	<Enum EnumName="Win32_OperatingSystem.ForegroundApplicationBoost" UnderlyingType="System.Byte">
    	<Value Name="None" Value="0" />
      	<Value Name="Minimum" Value="1" />
      	<Value Name="Maximum" Value="2" />
    </Enum>
</Enums>

Adding Client Side Confirmation

Whenever your cmdlet definitions define a ConfirmImpact, you need to make sure confirmation takes place on the client side. If you call your new cmdlets locally, this wouldn’t make a difference, but once you start using them to manage remote systems, you must ensure that the confirmation logic is invoked on the client and not the server.

That’s why you should add this xml to the Class definition:

<CmdletAdapterPrivateData>
	<Data Name="ClientSideShouldProcess" />
</CmdletAdapterPrivateData>

Updating CDXML Module

Let’s add all of this to our CDXML module and update it:

$testfolder = "$env:temp\cdxmlTest"

$cdxml = @'
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">

  <!--referencing the WMI class this cdxml uses-->
  <Class ClassName="root/cimv2/Win32_OperatingSystem" ClassVersion="2.0">
    <Version>1.0</Version>

    <!--default noun used by Get-cmdlets and when no other noun is specified. By convention, we use the prefix "WMI" and the base name of the WMI class involved. This way, you can easily identify the underlying WMI class.-->
    <DefaultNoun>WmiOperatingSystem</DefaultNoun>

    <!--define the cmdlets that work with class instances.-->
    <InstanceCmdlets>
      <!--query parameters to select instances. This is typically empty for classes that provide only one instance-->
      <GetCmdletParameters />
      
      <!--defines a new cmdlet that acts on instances-->
      <Cmdlet>
        <CmdletMetadata Verb="Set" ConfirmImpact="High"/>
        <!--modifies properties of an instance-->
        <Method MethodName="cim:ModifyInstance">
          <!--each parameter modifies one property-->
          <Parameters>
            <!--modifies property Description-->
            <Parameter ParameterName="Description">
              <Type PSType="string" />
              <CmdletParameterMetadata ValueFromPipelineByPropertyName="true">
                <AllowEmptyString />
                <ValidateNotNull />
              </CmdletParameterMetadata>
              
              <!--modifies property ForegroundApplicationBoost-->
              </Parameter>
              <Parameter ParameterName="ForegroundApplicationBoost">
                <!--uses a custom type that is defined later in Enums-->
                <Type PSType="Win32_OperatingSystem.ForegroundApplicationBoost" />
                <CmdletParameterMetadata ValueFromPipelineByPropertyName="true">
                  <ValidateNotNull />
                  <ValidateNotNullOrEmpty />
                </CmdletParameterMetadata>
              </Parameter>
            </Parameters>
          </Method>
          <GetCmdletParameters /> 
        </Cmdlet>

    </InstanceCmdlets>
    
    <CmdletAdapterPrivateData>
	  <Data Name="ClientSideShouldProcess" />
    </CmdletAdapterPrivateData>
  </Class>
  
  <!--defines custom types-->
  <Enums>
    <!--defines the custom type for ForegroundApplicationBoost-->
	<Enum EnumName="Win32_OperatingSystem.ForegroundApplicationBoost" UnderlyingType="System.Byte">
    	<Value Name="None" Value="0" />
      	<Value Name="Minimum" Value="1" />
      	<Value Name="Maximum" Value="2" />
    </Enum>
  </Enums>
</PowerShellMetadata>
'@

# create a test folder if it does not exist
$exists = Test-Path -Path $testfolder -PathType Container
if (!$exists) { $null = New-Item -Path $testfolder -ItemType Directory }

# write cdxml file
$path = Join-Path -Path $testfolder -ChildPath 'win32_operatingsystem.cdxml'
$cdxml | Set-Content -Path $path -Encoding UTF8

Once you import the module (again), you now get another new cmdlet: Set-WmiOperatingSystem:

# import cdxml file as a PowerShell module:
$testfolder = "$env:temp\cdxmlTest"
$path = Join-Path -Path $testfolder -ChildPath 'win32_operatingsystem.cdxml'
Import-Module -Name $path -Verbose -Force
VERBOSE: Loading module from path 'C:\Users\tobia\AppData\Local\Temp\cdxmlTest\win32_operatingsystem.cdxml'.
VERBOSE: Importing function 'Get-WmiOperatingSystem'.
VERBOSE: Importing function 'Set-WmiOperatingSystem'.

Using Set-WmiOperatingSystem

With Set-WmiOperatingSystem, it is now trivial to change writeable properties in Win32_OperatingSystem. Just make sure you have Administrator privileges, and you understand the changes you invoke.

For example, to assign a new description to your computer, run this:

#requires administrator privileges
Set-WmiOperatingSystem -Description 'My new description'

# check success:
Get-WmiOperatingSystem | Select-Object -Property Description
Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property Description

When you run Set-WmiOperatingSystem, you’ll see an automatic confirmation prompt popping up because I assigned a ConfirmImpact of High to this cmdlet. If you think the cmdlet action isn’t as severe, change it to Medium instead.

When you try and change the ForegroundApplicationBoost property, you see another pleasant surprise: the parameter *-ForegroundApplicationBoost does not require cryptic numbers. Instead, you get argument completion and friendly names and can choose from Maximum, Minimum, and None, thanks to the custom type and enumeration I defined earlier.

# set the operating system foreground application boost:
Set-WmiOperatingSystem -ForegroundApplicationBoost Maximum

The foreground application boost is a way to make Windows more responsive: the application in the foreground receives a higher CPU priority than other processes. That’s why you shouldn’t change this setting permanently to anything than Maximum if this is not a server.

Implementing Method Calls

Win32_OperatingSystem supports five methods, and you can add cmdlets for these methods as well to your CDXML. Implementing these cmdlets is very similar to the previous example: you basically define the cmdlet names and the required parameters that the methods take. Here is the xml definition:

<!--Invoke-OperatingSystemReboot: invoking method Reboot():-->
<Cmdlet>
  <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
  <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemReboot" ConfirmImpact="High" />
  <!--defining the WMI instance method used by this cmdlet:-->
  <Method MethodName="Reboot">
    <ReturnValue>
      <Type PSType="system.uint32" />
      <CmdletOutputMetadata>
        <ErrorCode />
      </CmdletOutputMetadata>
    </ReturnValue>
  </Method>
</Cmdlet>

<!--Set-OperatingSystemDateTime: invoking method SetDateTime():-->
<Cmdlet>
  <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
  <CmdletMetadata Verb="Set" Noun="WmiOperatingSystemDateTime" ConfirmImpact="High" />
  <!--defining the WMI instance method used by this cmdlet:-->
  <Method MethodName="SetDateTime">
    <ReturnValue>
      <Type PSType="system.uint32" />
      <CmdletOutputMetadata>
        <ErrorCode />
      </CmdletOutputMetadata>
    </ReturnValue>
    <!--defining the parameters of this cmdlet:-->
    <Parameters>
      <!--native parameter name is 'LocalDateTime'-->
      <Parameter ParameterName="LocalDateTime">
        <!--the underlying parameter type is DateTime which corresponds to the PowerShell .NET type [system.DateTime]-->
        <Type PSType="system.DateTime" />
        <CmdletParameterMetadata IsMandatory="false">
          <ValidateNotNull />
          <ValidateNotNullOrEmpty />
        </CmdletParameterMetadata>
      </Parameter>
    </Parameters>
  </Method>
</Cmdlet>

<!--Invoke-OperatingSystemShutdown: invoking method Shutdown():-->
<Cmdlet>
  <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
  <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemShutdown" ConfirmImpact="High" />
  <!--defining the WMI instance method used by this cmdlet:-->
  <Method MethodName="Shutdown">
    <ReturnValue>
      <Type PSType="system.uint32" />
      <CmdletOutputMetadata>
        <ErrorCode />
      </CmdletOutputMetadata>
    </ReturnValue>
  </Method>
</Cmdlet>

<!--Invoke-OperatingSystemWin32Shutdown: invoking method Win32Shutdown():-->
<Cmdlet>
  <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
  <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemWin32Shutdown" ConfirmImpact="High" />
  <!--defining the WMI instance method used by this cmdlet:-->
  <Method MethodName="Win32Shutdown">
    <ReturnValue>
      <Type PSType="system.uint32" />
      <CmdletOutputMetadata>
        <ErrorCode />
      </CmdletOutputMetadata>
    </ReturnValue>
    <!--defining the parameters of this cmdlet:-->
    <Parameters>
      <!--native parameter name is 'Flags'-->
      <Parameter ParameterName="Flags">
        <!--the underlying parameter type is SInt32 which really is the enumeration [Win32_OperatingSystem.Flags] that is defined below in the Enums node:-->
        <Type PSType="Win32_OperatingSystem.Flags" />
        <CmdletParameterMetadata IsMandatory="false">
          <ValidateNotNull />
          <ValidateNotNullOrEmpty />
        </CmdletParameterMetadata>
      </Parameter>
    </Parameters>
  </Method>
</Cmdlet>

<!--Invoke-OperatingSystemWin32ShutdownTracker: invoking method Win32ShutdownTracker():-->
<Cmdlet>
  <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
  <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemWin32ShutdownTracker" ConfirmImpact="High" />
  <!--defining the WMI instance method used by this cmdlet:-->
  <Method MethodName="Win32ShutdownTracker">
    <ReturnValue>
      <Type PSType="system.uint32" />
      <CmdletOutputMetadata>
        <ErrorCode />
      </CmdletOutputMetadata>
    </ReturnValue>
    <!--defining the parameters of this cmdlet:-->
    <Parameters>
      <!--native parameter name is 'Flags'-->
      <Parameter ParameterName="Flags">
        <!--the underlying parameter type is SInt32 which really is the enumeration [Win32_OperatingSystem.Flags] that is defined below in the Enums node:-->
        <Type PSType="Win32_OperatingSystem.Flags" />
        <CmdletParameterMetadata IsMandatory="false">
          <ValidateNotNull />
          <ValidateNotNullOrEmpty />
        </CmdletParameterMetadata>
      </Parameter>
      <!--native parameter name is 'Timeout'-->
      <Parameter ParameterName="Timeout">
        <!--the underlying parameter type is UInt32 which corresponds to the PowerShell .NET type [system.UInt32]-->
        <Type PSType="system.UInt32" />
        <CmdletParameterMetadata IsMandatory="false">
          <ValidateNotNull />
          <ValidateNotNullOrEmpty />
        </CmdletParameterMetadata>
      </Parameter>
      <!--native parameter name is 'Comment'-->
      <Parameter ParameterName="Comment">
        <!--the underlying parameter type is String which corresponds to the PowerShell .NET type [system.String]-->
        <Type PSType="system.String" />
        <CmdletParameterMetadata IsMandatory="false">
          <ValidateNotNull />
          <ValidateNotNullOrEmpty />
        </CmdletParameterMetadata>
      </Parameter>
      <!--native parameter name is 'ReasonCode'-->
      <Parameter ParameterName="ReasonCode">
        <!--the underlying parameter type is UInt32 which corresponds to the PowerShell .NET type [system.UInt32]-->
        <Type PSType="system.UInt32" />
        <CmdletParameterMetadata IsMandatory="false">
          <ValidateNotNull />
          <ValidateNotNullOrEmpty />
        </CmdletParameterMetadata>
      </Parameter>
    </Parameters>
  </Method>
</Cmdlet>

All methods related to rebooting take a parameter Flags which again is just a list of supported numbers. In this case, though, the numbers are bit flags and can be combined. This is why we need to define the flags like this and add the attribute BitwiseFlags:

<Enum EnumName="Win32_OperatingSystem.Flags" UnderlyingType="System.Int32" BitwiseFlags="true">
    <Value Name="Logoff" Value="0" />
    <Value Name="Shutdown" Value="1" />
    <Value Name="Reboot" Value="2" />
    <Value Name="Force" Value="4" />
    <Value Name="PowerOff" Value="8" />
</Enum>

Final Version: CDXML Module

Let’s add all of this to our CDXML module again and update it once more:

$testfolder = "$env:temp\cdxmlTest"

$cdxml = @'
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">

  <!--referencing the WMI class this cdxml uses-->
  <Class ClassName="root/cimv2/Win32_OperatingSystem" ClassVersion="2.0">
    <Version>1.0</Version>

    <!--default noun used by Get-cmdlets and when no other noun is specified. By convention, we use the prefix "WMI" and the base name of the WMI class involved. This way, you can easily identify the underlying WMI class.-->
    <DefaultNoun>WmiOperatingSystem</DefaultNoun>

    <!--define the cmdlets that work with class instances.-->
    <InstanceCmdlets>
      <!--query parameters to select instances. This is typically empty for classes that provide only one instance-->
      <GetCmdletParameters />
      
      <!--defines a new cmdlet that acts on instances-->
      <Cmdlet>
        <CmdletMetadata Verb="Set" ConfirmImpact="High"/>
        <!--modifies properties of an instance-->
        <Method MethodName="cim:ModifyInstance">
          <!--each parameter modifies one property-->
          <Parameters>
            <!--modifies property Description-->
            <Parameter ParameterName="Description">
              <Type PSType="string" />
              <CmdletParameterMetadata ValueFromPipelineByPropertyName="true">
                <AllowEmptyString />
                <ValidateNotNull />
              </CmdletParameterMetadata>
              
              <!--modifies property ForegroundApplicationBoost-->
              </Parameter>
              <Parameter ParameterName="ForegroundApplicationBoost">
                <!--uses a custom type that is defined later in Enums-->
                <Type PSType="Win32_OperatingSystem.ForegroundApplicationBoost" />
                <CmdletParameterMetadata ValueFromPipelineByPropertyName="true">
                  <ValidateNotNull />
                  <ValidateNotNullOrEmpty />
                </CmdletParameterMetadata>
              </Parameter>
            </Parameters>
          </Method>
          <GetCmdletParameters /> 
        </Cmdlet>

          <!--Invoke-OperatingSystemReboot: invoking method Reboot():-->
          <Cmdlet>
            <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
            <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemReboot" ConfirmImpact="High" />
            <!--defining the WMI instance method used by this cmdlet:-->
            <Method MethodName="Reboot">
              <ReturnValue>
                <Type PSType="system.uint32" />
                <CmdletOutputMetadata>
                  <ErrorCode />
                </CmdletOutputMetadata>
              </ReturnValue>
            </Method>
          </Cmdlet>

          <!--Set-OperatingSystemDateTime: invoking method SetDateTime():-->
          <Cmdlet>
            <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
            <CmdletMetadata Verb="Set" Noun="WmiOperatingSystemDateTime" ConfirmImpact="High" />
            <!--defining the WMI instance method used by this cmdlet:-->
            <Method MethodName="SetDateTime">
              <ReturnValue>
                <Type PSType="system.uint32" />
                <CmdletOutputMetadata>
                  <ErrorCode />
                </CmdletOutputMetadata>
              </ReturnValue>
              <!--defining the parameters of this cmdlet:-->
              <Parameters>
                <!--native parameter name is 'LocalDateTime'-->
                <Parameter ParameterName="LocalDateTime">
                  <!--the underlying parameter type is DateTime which corresponds to the PowerShell .NET type [system.DateTime]-->
                  <Type PSType="system.DateTime" />
                  <CmdletParameterMetadata IsMandatory="false">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>
              </Parameters>
            </Method>
          </Cmdlet>

          <!--Invoke-OperatingSystemShutdown: invoking method Shutdown():-->
          <Cmdlet>
            <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
            <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemShutdown" ConfirmImpact="High" />
            <!--defining the WMI instance method used by this cmdlet:-->
            <Method MethodName="Shutdown">
              <ReturnValue>
                <Type PSType="system.uint32" />
                <CmdletOutputMetadata>
                  <ErrorCode />
                </CmdletOutputMetadata>
              </ReturnValue>
            </Method>
          </Cmdlet>

          <!--Invoke-OperatingSystemWin32Shutdown: invoking method Win32Shutdown():-->
          <Cmdlet>
            <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
            <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemWin32Shutdown" ConfirmImpact="High" />
            <!--defining the WMI instance method used by this cmdlet:-->
            <Method MethodName="Win32Shutdown">
              <ReturnValue>
                <Type PSType="system.uint32" />
                <CmdletOutputMetadata>
                  <ErrorCode />
                </CmdletOutputMetadata>
              </ReturnValue>
              <!--defining the parameters of this cmdlet:-->
              <Parameters>
                <!--native parameter name is 'Flags'-->
                <Parameter ParameterName="Flags">
                  <!--the underlying parameter type is SInt32 which really is the enumeration [Win32_OperatingSystem.Flags] that is defined below in the Enums node:-->
                  <Type PSType="Win32_OperatingSystem.Flags" />
                  <CmdletParameterMetadata IsMandatory="false">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>
              </Parameters>
            </Method>
          </Cmdlet>

          <!--Invoke-OperatingSystemWin32ShutdownTracker: invoking method Win32ShutdownTracker():-->
          <Cmdlet>
            <!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
            <CmdletMetadata Verb="Invoke" Noun="WmiOperatingSystemWin32ShutdownTracker" ConfirmImpact="High" />
            <!--defining the WMI instance method used by this cmdlet:-->
            <Method MethodName="Win32ShutdownTracker">
              <ReturnValue>
                <Type PSType="system.uint32" />
                <CmdletOutputMetadata>
                  <ErrorCode />
                </CmdletOutputMetadata>
              </ReturnValue>
              <!--defining the parameters of this cmdlet:-->
              <Parameters>
                <!--native parameter name is 'Flags'-->
                <Parameter ParameterName="Flags">
                  <!--the underlying parameter type is SInt32 which really is the enumeration [Win32_OperatingSystem.Flags] that is defined below in the Enums node:-->
                  <Type PSType="Win32_OperatingSystem.Flags" />
                  <CmdletParameterMetadata IsMandatory="false">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>
                <!--native parameter name is 'Timeout'-->
                <Parameter ParameterName="Timeout">
                  <!--the underlying parameter type is UInt32 which corresponds to the PowerShell .NET type [system.UInt32]-->
                  <Type PSType="system.UInt32" />
                  <CmdletParameterMetadata IsMandatory="false">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>
                <!--native parameter name is 'Comment'-->
                <Parameter ParameterName="Comment">
                  <!--the underlying parameter type is String which corresponds to the PowerShell .NET type [system.String]-->
                  <Type PSType="system.String" />
                  <CmdletParameterMetadata IsMandatory="false">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>
                <!--native parameter name is 'ReasonCode'-->
                <Parameter ParameterName="ReasonCode">
                  <!--the underlying parameter type is UInt32 which corresponds to the PowerShell .NET type [system.UInt32]-->
                  <Type PSType="system.UInt32" />
                  <CmdletParameterMetadata IsMandatory="false">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>
              </Parameters>
            </Method>
          </Cmdlet>
    </InstanceCmdlets>
    
    <CmdletAdapterPrivateData>
	  <Data Name="ClientSideShouldProcess" />
    </CmdletAdapterPrivateData>
  </Class>
  
  <!--defines custom types-->
  <Enums>
    <!--defines the custom type for ForegroundApplicationBoost-->
	<Enum EnumName="Win32_OperatingSystem.ForegroundApplicationBoost" UnderlyingType="System.Byte">
    	<Value Name="None" Value="0" />
      	<Value Name="Minimum" Value="1" />
      	<Value Name="Maximum" Value="2" />
    </Enum>
    
    <!--defines the bits for the Flags argument that is used by all reboot methods-->
    <Enum EnumName="Win32_OperatingSystem.Flags" UnderlyingType="System.Int32" BitwiseFlags="true">
      <Value Name="Logoff" Value="0" />
      <Value Name="Shutdown" Value="1" />
      <Value Name="Reboot" Value="2" />
      <Value Name="Force" Value="4" />
      <Value Name="PowerOff" Value="8" />
    </Enum>
  </Enums>
</PowerShellMetadata>
'@

# create a test folder if it does not exist
$exists = Test-Path -Path $testfolder -PathType Container
if (!$exists) { $null = New-Item -Path $testfolder -ItemType Directory }

# write cdxml file
$path = Join-Path -Path $testfolder -ChildPath 'win32_operatingsystem.cdxml'
$cdxml | Set-Content -Path $path -Encoding UTF8

Once you import the module (again), you now get another new cmdlet: Set-WmiOperatingSystem:

# import cdxml file as a PowerShell module:
$testfolder = "$env:temp\cdxmlTest"
$path = Join-Path -Path $testfolder -ChildPath 'win32_operatingsystem.cdxml'
Import-Module -Name $path -Verbose -Force
VERBOSE: Removing the imported "Get-WmiOperatingSystem" function.
VERBOSE: Removing the imported "Set-WmiOperatingSystem" function.
VERBOSE: Loading module from path 'C:\Users\tobia\AppData\Local\Temp\cdxmlTest\win32_operatingsystem.cdxml'.
VERBOSE: Importing function 'Get-WmiOperatingSystem'.
VERBOSE: Importing function 'Invoke-WmiOperatingSystemReboot'.
VERBOSE: Importing function 'Invoke-WmiOperatingSystemShutdown'.
VERBOSE: Importing function 'Invoke-WmiOperatingSystemWin32Shutdown'.
VERBOSE: Importing function 'Invoke-WmiOperatingSystemWin32ShutdownTracker'.
VERBOSE: Importing function 'Set-WmiOperatingSystem'.
VERBOSE: Importing function 'Set-WmiOperatingSystemDateTime'.

Using Set-WmiOperatingSystemDateTime

Now when you need to set the date and time for a system locally or remotely, try Set-WmiOperatingSystemDateTime:

# set date and time (warning: do this only when you know what you are doing, and adjust the date below)
# admin privs required:
Set-WmiOperatingSystemDateTime -LocalDateTime '2020-05-04 12:00:00'

Logging Off And Rebooting

Logging off a user, shutting down, or restarting a system is now a snap. There are a number of new cmdlets to choose from. Invoke-WmiOperatingSystemWin32ShutdownTracker is the most sophisticated of them: it allows to specify a timeout, displays a user message on-screen, and logs the event:

# forcefully logging off user with a message and timeout (admin privs required)
Invoke-WmiOperatingSystemWin32ShutdownTracker -Flags Logoff,Force -Timeout 30 -Comment 'I log you off in 30 seconds because I can!' -ReasonCode 99

Next Steps

You’ve seen how powerful CDXML-based PowerShell modules are: you can add all the cryptic expert knowledge into the CDXML definition and get a bunch of simple-to-use cmdlets that no longer require the user to care about WMI class names, bitflags, data types, etc.

In this article I have looked at Win32_OperatingSystem which always exactly has one instance. In the next part, I’ll add cmdlets for another WMI class that can have more instances, and illustrate how you define query parameters.

Once that’s covered, we’ll look at adding type and format extensions so the data returned by WMI is no longer cryptic. And finally, we’ll look at how to generate help files for CDXML-defined modules. Stay tuned!