Quantcast
Channel: Hey, Scripting Guy! Blog
Viewing all articles
Browse latest Browse all 3333

Registry Cmdlets: Complete the Registry CDXML Module

$
0
0

Summary: Richard Siddaway shows how to complete the registry CDXML module.

Honorary Scripting Guy, Richard Siddaway, here today filling in for my good friend, The Scripting Guy. This is the final post in a series. To catch up, read:

So far, you’ve seen how to create a CDXML module with a single cmdlet based on the GetStringValue method of the StdRegProv CIM class. In this post, I’ll show you how to add the other methods that are available in the class to create a complete module of easy-to-use cmdlets for working with the registry. I’ll also show you how to work with some of the more awkward registry data types.

The complete module consists of these cmdlets:

Get-RegistryBinary

Get-RegistryDWORD

Get-RegistryExpandSTRING

Get-RegistryKey

Get-RegistryMultiSTRING

Get-RegistryQWORD

Get-RegistrySTRING

Get-RegistryValue

New-RegistryKey

Remove-RegistryKey

Remove-RegistryValue

Set-RegistryBinary

Set-RegistryDWORD

Set-RegistryExpandSTRING

Set-RegistryMultiSTRING

Set-RegistryQWORD

Set-RegistrySTRING

These cmdlets provide the ability to get and set values for each of the registry data types, get lists of registry keys and values, create new keys, and delete keys and values.

Reading data from the registry is very useful, but you also need to be able to write to the registry. You can use the SetStringValue method as an example:

      <Cmdlet>

        <CmdletMetadata Verb="Set" Noun="RegistrySTRING" ConfirmImpact="Low"/>

        <Method MethodName="SetSTRINGvalue">

          <ReturnValue>

            <Type PSType="System.UInt32"/>

            <CmdletOutputMetadata>

            </CmdletOutputMetadata>

          </ReturnValue>

         

          <Parameters>

            <Parameter ParameterName="hDefKey" >

              <Type PSType="RSPSNA.Registry.Hive" />

              <CmdletParameterMetadata IsMandatory="true" PSName="Hive">

              </CmdletParameterMetadata>

            </Parameter>

            <Parameter ParameterName="sSubKeyName" >

              <Type PSType="System.String" />

              <CmdletParameterMetadata PSName="SubKey">

                <ValidateNotNullOrEmpty />

              </CmdletParameterMetadata>

            </Parameter>

            <Parameter ParameterName="sValueName" >

              <Type PSType="System.String" />

              <CmdletParameterMetadata PSName="ValueName">

                <ValidateNotNullOrEmpty />

              </CmdletParameterMetadata>

            </Parameter>

            <Parameter ParameterName="sValue">

              <Type PSType="System.String"  />

              <CmdletParameterMetadata PSName="Value">

                <ValidateNotNullOrEmpty />

              </CmdletParameterMetadata>

            </Parameter>

          </Parameters>

        </Method>

      </Cmdlet>

In this case, you have to provide the hive, subkey, and value with the data to insert into that value. I’ve used ValueName and Value for the registry value and data respectively:

Set-RegistryString -Hive HKLM -SubKey SOFTWARE\ModuleTest -ValueName svalue -Value 'Summit2014'

The cmdlet will create the registry value if it doesn’t exist but the key needs to be present already. You can create registry keys as follows:

New-RegistryKey -Hive HKLM -SubKey software\moduletest

Other registry data types include:

  • Binary
  • DWORD – 32-bit number
  • QWORD – 64-bit number
  • Multistting = array of strings
  • Expanding string (usually used with environment variables)

When you look at registry values by using the EnumValues method, the output isn’t very easy to work with:

£> [uint32]$hklm = 2147483650

£> $key = 'software\moduletest'

£> Invoke-CimMethod -ClassName StdRegProv -MethodName EnumValues -Arguments @{hDefKey = $hklm; sSubKeyName = $key}

ReturnValue    : 0

sNames         : {dvalue, qvalue, svalue, mstring...}

Types          : {4, 11, 1, 7...}

PSComputerName :

You get an array of value names in sNames plus an array of data types in Types. The data types (as occurs so often in CIM) are coded as integer values. Ideally, you would want to see the data types output in a friendlier manner.

You can apply a similar technique to the way the numeric values for registry hives were dealt with. Start by creating an enumeration:

    <Enum EnumName="RSPSNA.Registry.Type" UnderlyingType="System.UInt32">

      <Value Name="String" Value="1" />

      <Value Name="ExpandingString" Value="2" />

      <Value Name="Binary" Value="3" />

      <Value Name="Dword" Value="4" />

      <Value Name="MultiString" Value="7" />

      <Value Name="Qword" Value="11" />

    </Enum>

Remember that the name part of the enumeration cannot contain spaces. If you need to break words, use capital letters to designate the start of words or use a delimiter (such as a hyphen).

The CDXML definition for the cmdlet created from the EnumValues method looks like this:

      <Cmdlet>

        <CmdletMetadata Verb="Get" Noun="RegistryValue" ConfirmImpact="Low"/>

        <Method MethodName="EnumValues">

          <ReturnValue>

            <Type PSType="System.UInt32"/>

            <CmdletOutputMetadata>

            </CmdletOutputMetadata>

          </ReturnValue>

         

          <Parameters>

            <Parameter ParameterName="hDefKey" >

              <Type PSType="RSPSNA.Registry.Hive" />

              <CmdletParameterMetadata IsMandatory="true" PSName="Hive">

              </CmdletParameterMetadata>

            </Parameter>

            <Parameter ParameterName="sSubKeyName" >

              <Type PSType="System.String" />

              <CmdletParameterMetadata PSName="SubKey">

                <ValidateNotNullOrEmpty />

              </CmdletParameterMetadata>

            </Parameter>

            <!-- Two output arrays -->

            <!--                   -->

            <Parameter ParameterName="sNames">

              <Type PSType="System.String[]"  />

              <CmdletOutputMetadata />

            </Parameter>

            <Parameter ParameterName="Types">

              <Type PSType="RSPSNA.Registry.Type[]" />

              <CmdletOutputMetadata />

            </Parameter>

          </Parameters>

        </Method>

      </Cmdlet>

You have two output arguments. The first is sNames, which is an array of strings that contain the value names. The second is an array that is created with the RSPSNA.Registry.Type enumeration supplying the values. You still have the issue of two output arrays, when ideally, you want one set of output objects. Unfortunately, CDXML can’t solve this issue, but a little bit of Windows PowerShell magic gives you the answer.

Using the cmdlet as-is results in this output:

£> Get-RegistryValue -Hive HKLM -SubKey software\moduletest

sNames      : {dvalue, qvalue, svalue, mstring...}

Types       : {Dword, Qword, String, MultiString...}

ReturnValue : 0

You can see that Types now contains the data type names rather than the numeric values. This would be usable for a registry key with only a handful of values, but ideally, you want something easier to read:

£> $values = Get-RegistryValue -Hive HKLM -SubKey software\moduletest

£> 0..$($values.sNames.Length-1) | foreach {New-Object -TypeName psobject -Property @{'Name'=$values.sNames[$psitem]; 'Type'=$values.Types[$psitem]}} | ft -a

Name               Type

----               ----

dvalue            Dword

qvalue            Qword

svalue           String

mstring     MultiString

SZvalue ExpandingString

Bvalue           Binary

If you store the output from Get-RegistryValue in a variable, you can access each array individually. Pipe numeric values that match the array indices – 0 to $values.sNames.Length-1 into Foreach-Object. Within the Foreach, use New-Object to create a PSObject that combines the matching name and data type. Output and use Format-Table to display, if you prefer.

By the way, don’t go looking for a Software\ModuleTest key in the HKLM hive on your system. It’s something I created for demonstration purposes.

Working with the integer data types, DWORD and QWORD, is similar to working with strings. The only change is that the output (or value input) property becomes a System.UInt32 or System.UInt64 respectively, and the name changes to uValue.

The MultiString data type holds an array of strings rather than a single string. The parameters working with the data are defined as an array, for instance:

            <Parameter ParameterName="sValue">

              <Type PSType="System.String[]"  />

              <CmdletOutputMetadata />

            </Parameter>

The remaining CDXML code is very similar to that for dealing with single string values. You can use the standard Windows PowerShell array handling techniques to work with the data.

To read the data:

$data = Get-RegistryMultiSTRING -Hive HKLM -SubKey SOFTWARE\ModuleTest -ValueName mstring  | select -ExpandProperty svalue

To remove a value from the array and write back:

$data = $data | where {$psitem -ne 'Value2'}

Set-RegistryMultiSTRING -Hive HKLM -SubKey SOFTWARE\ModuleTest -ValueName mstring  -Value $data

Get-RegistryMultiSTRING -Hive HKLM -SubKey SOFTWARE\ModuleTest -ValueName mstring

To add another value into the array and write back:

$data += 'Value6'

Set-RegistryMultiSTRING -Hive HKLM -SubKey SOFTWARE\ModuleTest -ValueName mstring  -Value $data

Get-RegistryMultiSTRING -Hive HKLM -SubKey SOFTWARE\ModuleTest -ValueName mstring

To reset the whole array:

$data = 'Value1', 'Value2', 'Value3'

Set-RegistryMultiSTRING -Hive HKLM -SubKey SOFTWARE\ModuleTest -ValueName mstring  -Value $data

Environment variables are some of those background pieces of functionality that no one really notices until they go wrong. They can be used in the registry. The data type is known as an expanding string (sounds like something from astrophysics), and you can use the cmdlets from your registry module like this:

Get-RegistryExpandSTRING -Hive HKLM -SubKey software\moduletest -ValueName SZvalue

Set-RegistryExpandSTRING -Hive HKLM -SubKey software\moduletest -ValueName SZvalue -Value '%TEMP%\mynewfolder'

Reading is just like reading a simple string value, but the result looks like this:

sValue

------

C:\windows\TEMP\myfolder

Notice that the result is automatically expanded for you. If you want to modify or create a registry value by using an environment variable, you code it like this:

'%TEMP%\mynewfolder'

The registry stores the data in that format until it’s accessed, at which point, the accessing routine should perform the expansion.

The last registry data type is the Binary type. I don’t like modifying these because bad things can happen if you get it wrong. But if you have to make changes, the safest way is to use the Set-RegistryBinary cmdlet from your module.  

CIM treats the binary data as an array of unsigned 8-bit integers. You need to convert this to a byte array for Windows PowerShell (.NET). The output argument when working with the GetBinaryValue method is coded like this:

            </Parameter>

            <Parameter ParameterName="uValue">

              <Type PSType="System.Byte[]"  />

              <CmdletOutputMetadata />

            </Parameter>

Reading data from binary registry values can be accomplished like this:

£> $values = Get-RegistryBinary -Hive HKLM -SubKey software\moduletest -ValueName Bvalue  | select -ExpandProperty uvalu

e

£> $values

0

1

2

3

4

5

6

12

Modifying binary data can be accomplished by changing the array and writing back:

£> $values += [byte]14

£> Set-RegistryBinary -Hive HKLM -SubKey software\moduletest -ValueName Bvalue -Value $values

0

A return code of 0 indicates success.

Using CDXML isn’t a technique for everyone. One issue is that there aren’t any good tools to make it easier to work with. The XML is relatively simple, but you need to be careful and precise in your coding. There are many modules in the later versions of Windows that are created by using CDXML.

You can use the technique to create similar modules on earlier versions of Windows by using existing CIM classes. For instance, the Win32_NetworkAdapter and Win32_NetworkAdapterConfiguration classes could be used to duplicate the NetAdapter and NetTCPIP modules respectively. If you have those modules installed on your down-level systems, and you use the same cmdlet names, you could manage all of your systems from what appears to be the same set of cmdlets.

This concludes my series about registry cmdlets. If you are interested in learning more about CDXML, my talk on this topic is available. Please see the PowerShell Summit North America 2014 site on YouTube. To download the slides, demo scripts, and code for each stage of module development (including the final version of the module), see my Dropbox site.

Bye for now.

Biography

Richard Siddaway is based out of the UK. He spends his time automating anything and everything, for Kelway, Ltd. A 7-year Windows PowerShell MVP, Richard is a prolific blogger, mainly about Windows PowerShell (see Richard Siddaway's Blog). Richard has been a director at PowerShell.org since the inception of that organization, and he is a frequent speaker at Windows PowerShell user groups and conferences. He has written a number of books: PowerShell in Practice, PowerShell and WMI, PowerShell in Depth (co-author), PowerShell Dive (co-editor), and Learn Active Directory Management in a Month of Lunches, which features lots of Windows PowerShell. All of the books are available from Manning Publications.

We invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys atscripter@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


Viewing all articles
Browse latest Browse all 3333

Trending Articles