Summary: Richard Siddaway shows how to start creating CDXML cmdlets.
Honorary Scripting Guy, Richard Siddaway, here today filling in for my good friend, The Scripting Guy. This is the second post in a series. Yesterday, in Registry Cmdlets: Working with the Registry, I explained that there aren’t any cmdlets for working directly with the registry. I showed some examples of using cmdlets that I’ve created to access the registry. The cmdlets are created from the WMI registry class, StdRegProv, by using CDXML.
In this post, I’ll show you how to get started with CDXML. Before that, though, I’d better explain CDXML...
Cmdlet definition XML (CDXML) is a way to create a Windows PowerShell module from a WMI class by using the cmdlets-over-objects technology that was introduced in Windows PowerShell 3.0. As usual, it’s always easier to show you an example:
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="ROOT\cimv2\Win32_BIOS">
<Version>1.0</Version>
<DefaultNoun>Bios</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters DefaultCmdletParameterSet="DefaultSet" >
</GetCmdletParameters>
</InstanceCmdlets>
</Class>
</PowerShellMetadata>
The first two lines in the previous command are header information, and they have to be present in all CDXML files. You also need to state the WMI class you want to use:
<Class ClassName="ROOT\cimv2\Win32_BIOS">
The namespace and class have to be supplied. This is equivalent to:
Get-CimInstance -Namespace root/CIMV2 -ClassName Win32_BIOS
You can only use a single WMI class in a CDXML module. If you need to access several WMI classes in your module, create a CDXML file per class, and use a module manifest to perform the load.
The version is arbitrary, and is up to you as to how it is changed as you change the module. Here is an example:
<Version>1.0</Version>
Windows PowerShell cmdlets have a noun-verb naming convention. CDXML allows you to set a default noun:
<DefaultNoun>Bios</DefaultNoun>
If you don’t supply a noun for the name of any cmdlet in your module, it will use the default noun.
The following four lines define the standard Get cmdlet in the module:
<InstanceCmdlets>
<GetCmdletParameters DefaultCmdletParameterSet="DefaultSet" >
</GetCmdletParameters>
</InstanceCmdlets>
The remaining XML closes the tags.
Save the XML with a .cdxml extension. I’ve used Bios.cdxml. You treat the CDXML file as a Windows PowerShell module. It’s most closely analogous to a .psm1 file. Create a folder on your module path with the same name as your module and save the file in that folder. The module will be automatically loaded in the same way as any other Windows PowerShell module when you start a Windows PowerShell session.
If you are working on the module in a series of stages, save it in a working folder that’s not on your module path. You can then load the module directly:
Import-Module .\bios.cdxml
If you are testing and debugging, you may need to load the module back into your Windows PowerShell session. You can ensure that you get the latest version of the module by using the –Force parameter:
Import-Module .\bios.cdxml –Force
£> Get-Module -Name Bios | Format-Table -AutoSize
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Cim 1.0 bios Get-Bios
The module contains a single cmdlet: Get-Bios. The module type shows as CIM. If you use a module manifest, the module type will change to Manifest.
If you use Get-Command on the cmdlet, for example:
£> Get-Command Get-Bios -Syntax
Get-Bios [-CimSession <CimSession[]>] [-ThrottleLimit <int>] [-AsJob] [<CommonParameters>]
...you’ll see that there are some parameters that you didn’t define: –CimSession, –ThrottleLimit, and –AsJob. These are always generated on a CDXML module. You get this functionality for free. You also get the standard common parameters without any effort on your part.
You use the cmdlet in the same manner as any other cmdlet:
£> Get-Bios
SMBIOSBIOSVersion : 2.04.0950
Manufacturer : American Megatrends Inc.
Name : 2.04.0950
SerialNumber : 036685734653
Version : OEMA - 1072009
This is the same data and format that you’d get by using the WMI class directly:
£> Get-CimInstance -ClassName Win32_BIOS
SMBIOSBIOSVersion : 2.04.0950
Manufacturer : American Megatrends Inc.
Name : 2.04.0950
SerialNumber : 036685734653
Version : OEMA - 1072009
You can use Get-Help on the cmdlet. It will only show the basic syntax. If you want extensive help, you need to create an XML-based external Help file for the module. There is no way to provide inline, comment-based Help like you can for a Windows PowerShell module.
Now that you’ve seen a basic CDXML module, let’s start putting together our Registry module. This code is the first step:
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="root\cimv2\StdRegProv">
<Version>1.0.0.0</Version>
<DefaultNoun>Registry</DefaultNoun>
<StaticCmdlets>
<Cmdlet>
<CmdletMetadata Verb="Get" Noun="RegistrySTRING" ConfirmImpact="Low"/>
<Method MethodName="GetSTRINGvalue">
<ReturnValue>
<Type PSType="System.UInt32"/>
<CmdletOutputMetadata>
</CmdletOutputMetadata>
</ReturnValue>
<Parameters>
<Parameter ParameterName="hDefKey" >
<Type PSType="System.UInt32" />
<CmdletParameterMetadata PSName="Hive">
</CmdletParameterMetadata>
</Parameter>
<Parameter ParameterName="sSubKeyName" >
<Type PSType="System.String" />
<CmdletParameterMetadata PSName="SubKey">
</CmdletParameterMetadata>
</Parameter>
<Parameter ParameterName="sValueName" >
<Type PSType="System.String" />
<CmdletParameterMetadata PSName="ValueName">
</CmdletParameterMetadata>
</Parameter>
<Parameter ParameterName="sValue">
<Type PSType="System.String" />
<CmdletOutputMetadata />
</Parameter>
</Parameters>
</Method>
</Cmdlet>
</StaticCmdlets>
</Class>
</PowerShellMetadata>
The first few lines define the class and version, as you’ve seen previously. I used Registry as the default noun. The next line is very important. In our Registry module, it is:
<StaticCmdlets>
The corresponding line in the BIOS example was:
<InstanceCmdlets>
The difference is that with the BIOS example, you are working with an instance of the object that exists. When you start your computer, the BIOS loads, and that information is available to you. You can’t create another instance of the BIOS!
Other classes where you are dealing with instances include Win32_LogicalDisk, Win32_ComputerSystem, and Win32_OperatingSystem. These are all objects that exist on your system, and therefore, they can have instances.
The Registry class is a little different. As you saw yesterday, the StdRegProv class provides a set of static methods for working with the registry. You don’t have instances in this case. The <StaticCmdlets> element tells the system that you are dealing with static members of the class rather than instances of CIM objects.
Eventually, the module will create a cmdlet for most methods in the StdRegProv class. To get a quick reminder of the methods available, you can use:
Get-CimClass -ClassName StdRegProv | select -ExpandProperty CimClassMethods
Many registry properties are strings, so I’m going to start with the GetStringValue method:
<CmdletMetadata Verb="Get" Noun="RegistrySTRING" ConfirmImpact="Low"/>
<Method MethodName="GetSTRINGvalue">
The cmdlet metadata sets the verb as Get and the noun as RegistrySTRING (this is capitalized for demonstration purposes, but feel free to revert to normal Windows PowerShell case conventions if you desire). This cmdlet will be called Get-RegistrySTRING and it will use the GetSTRINGvalue method.
The methods in CIM classes return data and a return value. If the return value is zero (0), it worked correctly. Any other value indicates failure. A return value is useful when troubleshooting registry issues, so you should include it in your cmdlet:
<ReturnValue>
<Type PSType="System.UInt32"/>
<CmdletOutputMetadata>
</CmdletOutputMetadata>
</ReturnValue>
The type is very important. It must be System.UInt32, that is, an unsigned 32-bit integer. CIM works with unsigned integers, which only have positive values:
£> [uint32]::MinValue
0
£> [uint32]::MaxValue
4294967295
In contrast, the standard integer can take positive and negative values:
£> [int32]::MinValue
-2147483648
£> [int32]::MaxValue
2147483647
The last step in defining the cmdlet is to supply the information that is required to define its parameters. Each parameter is defined in a separate parameter block. The GetStringValue method has four arguments—three input arguments and one output argument.
Get-CimClass and the WMI documentation refer to method parameters rather than arguments. To avoid additional confusion, I'm using arguments when I am talking about the method and I'm using parameters when I am talking about the resultant cmdlet.
The first input argument, hDefKey, defines the hive to be accessed. Remember that this is a number, for instance:
HKEY_LOCAL_MACHINE = 2147483650 (0x80000002)
It is supplied as an unsigned integer and the method argument name is overridden, so the cmdlet parameter name is Hive rather than hDefKey. This isn’t necessary, but it makes the cmdlet easier to use. The name of your cmdlet parameter is your choice—use whatever makes the cmdlet easier to use for you.
<Parameter ParameterName="hDefKey" >
<Type PSType="System.UInt32" />
<CmdletParameterMetadata PSName="Hive">
</CmdletParameterMetadata>
</Parameter>
The sSubKeyName and sValueName arguments for GetStringValue define the value you are reading from the registry. Both use "System.String" as their type, and I’ve overridden the argument names so the cmdlet’s parameters are SubKey and ValueName respectively:
<Parameter ParameterName="sSubKeyName" >
<Type PSType="System.String" />
<CmdletParameterMetadata PSName="SubKey">
</CmdletParameterMetadata>
</Parameter>
<Parameter ParameterName="sValueName" >
<Type PSType="System.String" />
<CmdletParameterMetadata PSName="ValueName">
</CmdletParameterMetadata>
</Parameter>
The final argument for the GetStringValue method is sValue. This again is a string, but it is the property for the return object that holds the value read from the registry. <CmdletOutputMetadata /> is required to ensure the data is output:
<Parameter ParameterName="sValue">
<Type PSType="System.String" />
<CmdletOutputMetadata />
</Parameter>
Note Renaming output parameters doesn’t seem to be possible.
Save the file with the CDXML definition as registry1.cdxml. You can load it into Windows PowerShell:
£> Get-Command -Module registry1
CommandType Name ModuleName
----------- ---- ----------
Function Get-RegistrySTRING registry1
£> Get-Command Get-RegistrySTRING -Syntax
Get-RegistrySTRING [-Hive <uint32>] [-SubKey <string>] [-ValueName <string>] [-CimSession <CimSession[]>] [-ThrottleLimit <int>] [-AsJob] [-WhatIf] [-Confirm] [<CommonParameters>]
You can use the following commands to use it.
To define the hive to use (in this case, HKLM):
[uint32]$hklm = 2147483650
To define the subkey and value to read:
$subkey = "SOFTWARE\Microsoft\Internet Explorer"
$value = "Version"
To use the cmdlet:
Get-RegistrySTRING -Hive $hklm -SubKey $subkey -ValueName $value
This gives the following result:
sValue ReturnValue
------ -----------
9.11.9600.17498 0
Now you know how to get started creating cmdlets for working with the registry. This series will continue tomorrow when I will show you how to change the code to enable you to use friendly names for the registry hives, make parameters mandatory, and perform validation on the values input to parameters.
We invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. Until then, remember eat your cmdlets every day with a dash of creativity.
Richard Siddaway, Windows PowerShell MVP and Honorary Scripting Guy