Converting Group Policies to DSC configurations

Desired State Configuration is a powerful tool in any Windows environment when it comes to automation. In customer projects, I personally like to do a greenfield approach whenever possible.

However, there are certainly situations where taking something that already exists might be beneficial. This was the case at one customer, who wanted to convert Group Policies to DSC configurations in order to test them. Suffice to say, my interest was piqued.

The main “difficulty” that they experienced was the link order of the policies. It is usually the case at most of my customers that there are several policies linked to an organizational unit (OU) which may or may not override each others settings based on the link order.

At the beginning we started out with a bunch of policy backups that had been generated by the customer’s security department. Faced with the huge task of verifying their entire infrastructure against their security baselines captured in various GPOs, they approached me and my colleague Andreas Mirbach, security expert.

We quickly laid out a plan of which items could be extracted from a GPO and which settings would either require external resources or custom code. For me that meant parsing the group policy exports for Registry Keys, User Rights Assignments as well as Security Options.

The same idea would apply to Microsoft’s security baselines as well, which you can download at . In order to create the data in a format that we can work well with in PowerShell, download the Policy Analyzer from the link. The documentation to create AuditPolicy xml files is very good and should contain everything you need to get started.

Getting started with the exported files

So, how do you start with a task like this? Well, first of all examining the source data is always a good choice. Since group policy exports in the PolicyRules format are always XML, we can quickly find the best tool for the job: XPATH. Take a look at chapter 5 of my book “PowerShell Core 6.2 Cook Book” for some examples regarding XPATH or “Learn PowerShell Core 6.0” for fundamental information on PowerShell scripting (no affiliate links here, but I am the author/co-author of those books).

First of all, we should import the data as an XmlDocument. Casting with the type accelerator xml does exactly that. If you examine the output on the console and with our trusty friend, the Get-Member cmdlet, you will notice this as well.

The output of Get-Member for the imported XML content

XPATH works with selector strings, so to get to the registry settings, we could apply the XPATH expression “/PolicyRules/ComputerConfig”. This is a simple one – all ComputerConfig nodes in the XML document will be returned. The best thing is: All child nodes can be accessed by their name through dot-notation, just like an object property.

Dot-notation on XML documents

Similarly, we can access the other entries. This time, the XPATH filter needs some more logic. The privilege rights for example are not accessible by normal means. Instead we need to filter on an XML attribute called Section with the value “Privilege Rights”. The resulting XPATH expressions looks like this: /PolicyRules/SecurityTemplate[@Section = “Privilege Rights”]/LineItem

The XPATH expression filters all SecurityTemplate nodes by examining their attribute Section. This is a pretty simple expression. For a more complex example, checkout chapter 12 of my book “PowerShell Core 6.2 Cook Book”, where I use XPATH to efficiently filter the Windows event log.

Deriving order from the exported files

We decided on the following naming convention for each policy export: <Publisher> – <OS> – <Version> – <Computer or User> – <Layer> – <Policy name>, for example “SecOps – Srv2019 – 1.1.3 – Computer – Baseline – AdministrativeTemplates”. This pattern lends itself to easy extraction using either regular expressions or even simpler: the Split operator. Pay special attention to the Layer portion, for example “Baseline”. This is the only indicator that we will be using in the first example to build a hierarchy.

Let’s assume that we have three layers: Baseline, ServerBase and DomainControllerBase with each layer being more specific than the previous and potentially overwriting settings. If you recall Desired State Configuration, for example from our book “Learn PowerShell Core 6.0”, you might remember that a configuration may not configure the same resource twice.

Since I needed to take care of the hierarchy either way, I simply allowed the user of the cmdlet to specify the hierarchy in an array.

    @('Baseline', 'Serverbase'),
    @('Baseline', 'Serverbase', 'DomainControllerBase')

This array means: First, create a Baseline configuration, then create a second configuration that merges Serverbase onto Baseline, and lastly create a configuration that merges Serverbase on Baseline, and DomainControllerBase on the result. Each configuration later would produce one MOF (managed object format) file to use for example with the Test-DscConfiguration cmdlet.

We can achieve this by first cleverly grouping the policy exports, so that we can access them through their layer!

$gpoFiles = Get-ChildItem -File -Path $Path |
    Group-Object { ($_.BaseName -split ' - ')[4] } -AsHashTable -AsString

With the parameters AsHashTable and AsString, the files will be grouped by their BaseName property, the file name without extension, and will be accessible through this basename. We apply the split operator to split the string since we can assume that each file looks the same.

With that done, we can simply iterate over each array-entry (i.e. each layer) and each hierarchy inside a layer. So two nested foreach loops, essentially.

foreach ($layer in $Precedence)
    foreach ($innerLayer in @($layer))

We need to cast $layer into an array since PowerShell helpfully unwraps single element arrays otherwise. In this case it would mean that $innerLayer would contain each individual letter of e.g. the layer Baseline.

To contain the values in a more useful way, you could create a new object. Using the type accelerator PSCustomObject, you can simply cast a hashtable to an object with note properties – job done.

$null = New-Variable -Name "$($innerLayer)items" -Value @{
            RegistryItems        = @()
            UserRightsAssignment = @()
            SecurityOptions      = @()
            AuditPol             = @()

Throughout the cmdlet I am making use of the variable cmdlets. Though they are not used often there are cases like dynamically created variables, where these cmdlets are very helpful.

The next bit is just plain PowerShell. Go through all elements matching the various XPATH filters, extract the information and add said information to the custom object. After the policy files have been sufficiently parsed, we can assemble the information. In the end, this was pretty simple.

Look through the previous layer. If the item exists, overwrite it. If not, add it. Make the previous layer the current layer and continue to progress. After iterating over all layers, the result is a list of custom objects, with the right-most entry in each layer winning.

Generating DSC code from the GPO

I took the simple approach and instead of fiddling with the AST I simply appended text to a file. In my case, the file name was the same as my layer, e.g. Base_ServerBase_DomainControllerBase to indicate how the configuration was merged. Again, a pretty basic approach – loop through all your custom objects and build DSC code from them. The following is an example for a registry key:

    ValueName = "$($item.ValueName)"
    ValueData= "$($item.ValueData -join '","')"
    ValueType = "$($Item.ValueType)"
    Key = "$($item.Key)"

Lastly, after all file contents have been written, the cmdlet compiles all configurations. Both the configurations as well as the configuration scripts are exported and can be reused.

foreach ($file in (Get-ChildItem "$PSScriptRoot/Configurations" -File))
    . $file.FullName
    & $file.BaseName -OutputPath "$PSScriptRoot/ReferenceMOFs/$($file.BaseName)"

Stay tuned for part 2, where we build a full testing environment, deploy our MOFs to it and gather reporting data in a nice Power BI dashboard with DSCEA!