Managing large numbers of registry settings with PowerShell DSC

Recently, I had to manage the configuration of the remote control settings of client machines with PowerShell DSC. These settings are located in the following registry key : HKLM:\SYSTEM\CurrentControlSet\Services\HidIr\Remotes, and they look like this :

RemoteRegistrySettings

Yes, this is 19 registry values for every single remote control model.

Here is what a resource entry in a DSC configuration would look like, using the built-in Registry resource :

Registry IRRemotes
{
        Ensure = "Present"
        Key = "HKLM:\SYSTEM\CurrentControlSet\Services\HidIr\Remotes\745a17a0-74d3-11d0-b6fe-00a0c90f57da"
        ValueName = "CodeMatchMask"
        ValueData = "4294905600"
        ValueType = "Dword"
}

 
This is for a single registry value.
So, we take this, we multiply it by 19 values and then, we multiply it by 6 remote control models and the result is : 684 lines of code.
This is going to be a pain to write and a nightmare to maintain.

So, when the line count of a DSC configuration jumps like this, we should take a step back and ask ourselves questions like these :

  • What is the impact on the readability and the maintainability of the DSC configuration (or more generally, what kind of technical debt this could create) ? And remember, DSC configurations are supposed to be more or less human-readable.
  •  

  • If we use (or plan to use) DSC configurations as “Documentation as code”, do we really need these details in your documentation ?
  •  

  • Is the business value provided/enabled by this code greater than the cost and time to write, read, test and maintain it ? Off course, these are going to be estimations, but we could even make up a metric, like the ratio business value per line of code (€/line). Then, we could decide that if this metric is less than a certain number, we don’t do it (or we need to do it another way).
  •  

  • Is there another way to achieve the same result ?

Once I answered all of these questions, I thought : “There has to be a better way”.

I couldn’t find any, so I wrote a custom DSC resource which is better suited at handling large numbers of registry settings (especially registry keys with many subkeys and values).
The name of both the module and the resource is cRegFile.

How does it work ?

Basically, it uses :

  • .reg files to contain all the settings in a managed registry key
  • reg.exe to import and export .reg files
  • Get-FileHash to compare the contents of .reg files

For the nitty-gritty, you can have a look at the code. As usual, the module is on GitHub :
https://github.com/MathieuBuisson/Powershell-Administration/tree/master/cRegFile

The .reg file specified in a DSC configuration using this resource represents the desired state for a registry key.
So, it contains the managed registry key, with all its subkeys and values, recursively.

This reference .reg file first needs to be generated.
To do that, we get a reference machine, make sure its registry key has all the settings we want, with all the values we want.
Then we export the registry key, from regedit >> Right-click >> Export , or with a “reg.exe export” command. Either way, the content and the format of the .reg file are the same.

The cRegFile resource is pretty simple to use, as we can see looking at its syntax :

C:\> Get-DscResource -Name cRegFile -Syntax

cRegFile [String] #ResourceName
{
     Key = [string]
    [DependsOn = [string[]]]
    [PsDscRunAsCredential = [PSCredential]]
    [RegFilePath = [string]]
}

 
Now, going back to our remote control settings, let’s configure all the registry values for all the remote control models that we want to support.
To do that, we add the following to our DSC configuration :

        File RemotesRegFile
        {
            DestinationPath = $($Node.RegFileFolder) + "RemotesKey.reg"
            SourcePath = "\\DevBox\Share\RemotesKey.reg"
            Ensure = "Present"
            Type = "File"
            Credential = $Credential
            Checksum = "SHA-1"
            Force = $true
            MatchSource = $true
        }
        cRegFile SupportedRemoteControls
        {
            key = "HKLM:\SYSTEM\CurrentControlSet\Services\HidIr\Remotes"
            RegFilePath = $($Node.RegFileFolder) + "RemotesKey.reg"
            DependsOn = "[File]RemotesRegFile"
        }

 
In case you are wondering what is $Node.RegFileFolder, this is a way to not hard-code the path in the configuration and get its value from the configuration data.

Also, notice the file resource entry. This is because the reg.exe import command doesn’t support remote files, so we first need to copy the .reg file to the target node, to be able to use it with the cRegFile resource.

Because something needs to happen in the File resource before what needs to happen in the cRegFile resource, we add a DependsOn property to our cRegFile resource entry to set the order in which things can happen.

As we can see, this is much cleaner than 684 lines. So, whenever there are more than a few registry values to manage within the same key, this resource makes the DSC configurations much shorter than with the built-in Registry resource.
Also, it probably runs faster (though I didn’t do any measured comparisons).

OK, the old-school reg.exe is not pure PowerShell, but the PowerShell story regarding the registry is not ideal (still using PSDrives, seriously ?). Reg.exe is fast, easy to use, battle-tested reliable.
More interestingly, it is surprisingly close to the philosophy of DSC : the desired state is defined in a “declarative” text file and the “Make it so” command : reg.exe import is idempotent.

I encourage you to grab it here and use it.

UPDATE : the module is now available in the PowerShell Gallery, so it can be installed right from a PowerShell console with Install-Module.

Leave a Reply

Your email address will not be published. Required fields are marked *