Summary: Richard Siddaway shows some advanced features in CDXML.
Honorary Scripting Guy, Richard Siddaway, here today filling in for my good friend, The Scripting Guy. This is the third post in a series. To catch up, read:
Yesterday, you saw that you can create a Windows PowerShell cmdlet from the StdRegProv CIM class by using CDXML, which enables you to read a string value from the registry. Today, I want to show you some of the advanced features of CDXML. These enable you to make your CDXML cmdlet act like any other Windows PowerShell cmdlet. For instance, you can make parameters mandatory and validate the values that are input to parameters.
Before that though, I want to show something that makes working with the registry so much easier. One of the biggest pain points when working with the CIM Registry provider, StdRegProv, is those awful numbers you have to remember—the numbers that define the hives:
HKEY_CLASSES_ROOT = 2147483648 (0x80000000)
HKEY_CURRENT_USER = 2147483649 (0x80000001)
HKEY_LOCAL_MACHINE = 2147483650 (0x80000002)
HKEY_USERS = 2147483651 (0x80000003)
HKEY_CURRENT_CONFIG = 2147483653 (0x80000005)
HKEY_DYN_DATA = 2147483654 (0x80000006)
They are required when using the cmdlet we created yesterday:
[uint32]$hklm = 2147483650
$subkey = "SOFTWARE\Microsoft\Internet Explorer"
$value = "Version"
Get-RegistrySTRING -Hive $hklm -SubKey $subkey -ValueName $value
But wouldn’t it be great if you could provide a friendly value for the hive—something like this:
Get-RegistrySTRING -Hive HKLM -SubKey $subkey -ValueName $value
There is a simple way to achieve this. The first step is to change the end of your CDXML file from this:
</Class>
</PowerShellMetadata>
...to this:
</Class>
<Enums>
<Enum EnumName="RSPSNA.Registry.Hive" UnderlyingType="System.UInt32">
<Value Name="HKCR" Value="2147483648" />
<Value Name="HKCU" Value="2147483649" />
<Value Name="HKLM" Value="2147483650" />
<Value Name="HKUS" Value="2147483651" />
<Value Name="HKLocalMachine" Value="2147483650" />
<Value Name="HKCC" Value="2147483653" />
</Enum>
</Enums>
</PowerShellMetadata>
You are adding an enumeration definition to the CDXML file. It has a name (in this case, EnumName="RSPSNA.Registry.Hive") and an underlying type (- UnderlyingType="System.UInt32"). You then need to define the values in the enumeration:
<Value Name="HKCR" Value="2147483648" />
Each value has a name-value pair as shown. It is possible to define multiple names for the same value:
<Value Name="HKLM" Value="2147483650" />
<Value Name="HKLocalMachine" Value="2147483650" />
This means that you can provide your users with a number of different ways of defining the hive key, and they can use whichever fits their style. I prefer the 4-letter acronyms—it’s less typing!
You also have to change the parameter definitions. Yesterday, you saw this command to define the hive parameter:
<Parameter ParameterName="hDefKey" >
<Type PSType="System.UInt32" />
<CmdletParameterMetadata PSName="Hive">
</CmdletParameterMetadata>
</Parameter>
You need to change it to use the enumeration:
<Parameter ParameterName="hDefKey" >
<Type PSType="RSPSNA.Registry.Hive" />
<CmdletParameterMetadata PSName="Hive">
</CmdletParameterMetadata>
</Parameter>
The only change is on the Type line where the type is changed to match the name of the enumeration. The name of the type, and therefore the enumeration, is arbitrary and under your control. I recommend that you create a naming scheme for enumerations in your organization.
In my testing, I named this version of the module registry2.cdxml. You import the module and set the values for the subkey and value you want to read:
Import-Module .\registry2.cdxml -Force
$subkey = "SOFTWARE\Microsoft\Internet Explorer"
$value = "Version"
And then use the cmdlet like this:
Get-RegistrySTRING -Hive HKLM -SubKey $subkey -ValueName $value
The act of defining an enumeration automatically forces validation of the input:
Get-RegistrySTRING -Hive HKLL -SubKey $subkey -ValueName $value
Get-RegistrySTRING : Cannot process argument transformation on parameter 'Hive'. Cannot convert value "HKLL" to type
"Microsoft.PowerShell.Cmdletization.GeneratedTypes.RSPSNA.Registry.Hive". Error: "Unable to match the identifier name
HKLL to a valid enumerator name. Specify one of the following enumerator names and try again: HKCR, HKCU, HKLM,HKLocalMachine, HKUS, HKCC"
At line:1 char:26
+ Get-RegistrySTRING -Hive HKLL -SubKey $subkey -ValueName $value
+ ~~~~
+ CategoryInfo : InvalidData: (:) [Get-RegistrySTRING], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Get-RegistrySTRING
As with other Windows PowerShell cmdlets, you even get a helpful hint as to the accepted values. As a side note, Tab completion works on the enumeration, so type:
Get-RegistrySTRING –Hive
...and then use the Tab key to cycle through the allowed values.
The hive defaults to HKLM, so you can do this:
Get-RegistrySTRING -SubKey $subkey -ValueName $value
This is not a practice I recommend, though. If you explicitly state the registry hive you want to access, it makes troubleshooting easier.
You can add other validation to the cmdlet parameters in a similar manner to advanced functions. The simplest change is to make a parameter mandatory. Add the IsMandatory="true" element to the CmdletParameterMetadata node, and you now have a mandatory parameter:
<Parameter ParameterName="hDefKey" >
<Type PSType="RSPSNA.Registry.Hive" />
<CmdletParameterMetadata IsMandatory="true" PSName="Hive">
</CmdletParameterMetadata>
</Parameter>
Like with an existing cmdlet or an advanced function, if you don’t specify the value of a mandatory parameter you will be prompted.
This works as you would expect:
Import-Module .\registry3.cdxml -Force
Get-RegistrySTRING -Hive HKLM -SubKey "SOFTWARE\Microsoft\Internet Explorer" -ValueName Version
Registry3.cdxml is the version where I added the mandatory parameter. If you don’t specify the –Hive parameter, you will be prompted:
£> Get-RegistrySTRING -SubKey "SOFTWARE\Microsoft\Internet Explorer" -ValueName Version
cmdlet Get-RegistrySTRING at command pipeline position 1
Now, supply values for the parameters—you won’t be allowed to specify the parameter without a value:
£> Get-RegistrySTRING -Hive -SubKey "SOFTWARE\Microsoft\Internet Explorer" -ValueName Version
Get-RegistrySTRING : Missing an argument for parameter 'Hive'. Specify a parameter of type
'Microsoft.PowerShell.Cmdletization.GeneratedTypes.RSPSNA.Registry.Hive' and try again.
At line:1 char:20
+ Get-RegistrySTRING -Hive -SubKey "SOFTWARE\Microsoft\Internet Explorer" -ValueN ...
+ ~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-RegistrySTRING], ParameterBindingException
+ FullyQualifiedErrorId : MissingArgument,Get-RegistrySTRING
There are a set of tags for setting validation on parameter input; for instance, to validate that the input isn’t null or empty:
<Parameter ParameterName="sValueName" >
<Type PSType="System.String" />
<CmdletParameterMetadata PSName="ValueName">
<ValidateNotNullOrEmpty />
</CmdletParameterMetadata>
</Parameter>
After importing the changed module, you can use it as before:
Get-RegistrySTRING -Hive HKLM -SubKey "SOFTWARE\Microsoft\Internet Explorer" -ValueName Version
If you don’t specify the Subkey value, you will receive an error message:
£> Get-RegistrySTRING -Hive HKLM -SubKey -ValueName Version
Get-RegistrySTRING : Missing an argument for parameter 'SubKey'. Specify a parameter of type 'System.String' and try again.
At line:1 char:31
+ Get-RegistrySTRING -Hive HKLM -SubKey -ValueName Version
+ ~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-RegistrySTRING], ParameterBindingException
+ FullyQualifiedErrorId : MissingArgument,Get-RegistrySTRING
If you don’t specify the parameter at all, you get a different error message:
£> Get-RegistrySTRING -Hive HKLM -ValueName Version
Get-RegistrySTRING : Invalid parameter
At line:1 char:1
+ Get-RegistrySTRING -Hive HKLM -ValueName Version
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (StdRegProv:root\cimv2\StdRegProv) [Get-RegistrySTRING], CimException
+ FullyQualifiedErrorId : HRESULT 0x80041008,Get-RegistrySTRING
If you specify the SubKey but not the value you’ll get an error:
Get-RegistrySTRING -Hive HKLM -SubKey "SOFTWARE\Microsoft\Internet Explorer"
sValue ReturnValue
------ -----------
1
Remember that a non-zero return code from a CIM method means that there has been an error.
A good way to discover examples of validation in CDXML modules is to view the files on a system running Windows 8 or Windows Server 2012 (or later versions). You’ll find the examples in the subfolders of C:\Windows\System32\WindowsPowerShell\v1.0\modules.
There are a lot of modules in these systems, and over 60% are created using CDXML. Here is a quick way to search for interesting examples:
Get-ChildItem -Path $pshome\modules -Filter *.cdxml -File -Recurse | Select-String -Pattern '*validate*' -SimpleMatch
Here are some examples of using validation in CDXML that I discovered in this way:
<ValidateNotNull /> is similar to <ValidateNotNullOrEmpty />, which you’ve seen already. I prefer to use <ValidateNotNullOrEmpty />.
To validate on a set of values:
<ValidateSet>
<AllowedValue>Both</AllowedValue>
<AllowedValue>Incoming</AllowedValue>
<AllowedValue>Outcoming</AllowedValue>
</ValidateSet>
This is taken from C:\Windows\System32\WindowsPowerShell\v1.0\modules\Defender\MSFT_MpPreference.cdxml.
You can also validate on a range as shown in C:\Windows\System32\WindowsPowerShell\v1.0\modules\MMAgent\ps_mmagent_v1.0.cdxml:
<ValidateRange Min="1" Max="8192" />
You might also find examples of other validation options, such as:
<ValidateCount Min="1" Max="10" />
<ValidateLength Min="1" Max="10" />
ValidatePattern and ValidateScript from advanced functions are not allowed in CDXML. You can have multiple validations on the same parameter, but remember that once one fails, that’s it—your input is rejected.
If you require them, you also have some options to allow specific scenarios:
<AllowEmptyCollection />
<AllowEmptyString />
<AllowNull />
That’s it for today. Tomorrow I’ll finish this short series with a look at some of the issues that arise when you start working with registry data types other than strings.
Bye for now.
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