PSA: Locating Bad/Corrupt Registry.POL Files

For several months – a good chunk of 2017 in fact – we’ve encountered machines where Group Policy failed as evidenced by the following on affected machines:

  • Seeing errors like the following when runing gpupdate /force:

    Computer policy could not be updated successfully.   The following errors were encountered:
    The processing of Group Policy failed.  Windows could not apply the registry-based settings for the Group Policy object LocalGPO.  Group Policy settings will not be resolved until this event is resolved.  View the event details for more information on the file name and path that caused the failure.
    To diagnose the failure, review the event log or run GPRESULT /H GPReport.html from the command line to access information about Group Policy results.

  • Event ID 1096 which itself would show ErrorCode 13 with an ErrorDescription of ‘The data is invalid.’ along with the problematic Registry.POL file.

This is a well known and well documented problem:

The cause: Unknown but we have some suspects.

The fix is easy: Delete or rename the problematic Registry.pol file, which so far is always in %SystemRoot%\System32\GroupPolicy\Machine

But that’s reactionary and we want to be as proactive as possible.

Enter: Test-IsRegistryPOLGood

I spent a bunch of time trying to figure out an intelligent manner of doing this, and after a lot of trial & error I was seeing inconsistent results.  After really getting into the weeds I wondered whether or not the structure/format of the file was documented.  Turns out it is: https://msdn.microsoft.com/en-us/library/aa374407(v=vs.85).aspx)

The header contains the REGFILE_SIGNATURE expressed as 0x67655250 which is 80, 82, 101, 103 in bytes:


[System.BitConverter]::GetBytes(0x67655250)

The header is in the first 4 bytes of the file so we read that and evaluate the bytes


Get-Content -Encoding Byte -Path 'path\to\someRegistry.pol' -TotalCount 4

On a good file, this returns an array consisting of 80, 82, 101, 103 which is exactly what we want.

On a bad file – or at least all the ones I had access to – it returned all zero’s.

To examine the file via PowerShell:


Function Test-IsRegistryPOLGood
    {
        [cmdletbinding()]
        Param
            (
                [Parameter(Mandatory=$false)]
                    [string[]]$PathToRegistryPOLFile = $(Join-Path $env:windir 'System32\GroupPolicy\Machine\Registry.pol')
            )

        if(!(Test-Path -Path $PathToRegistryPOLFile -PathType Leaf)) { return $null }

        [Byte[]]$FileHeader = Get-Content -Encoding Byte -Path $PathToRegistryPOLFile -TotalCount 4

        if(($FileHeader -join '') -eq '8082101103') { return $true } else { return $false }
    }

However, I wasn’t sure how we were going to implement this, so I explored doing this via VBScript and found ADO to be the ideal (only?) way:


Option Explicit
Dim arrRegistryPolFiles
if(WScript.Arguments.Count > 0) then
    arrRegistryPolFiles = Array(WScript.Arguments(0))
else
    arrRegistryPolFiles = Array(CreateObject("WScript.Shell").ExpandEnvironmentStrings("%WINDIR%") & "\System32\GroupPolicy\Machine\Registry.pol")
end if

Dim POLFile
For each POLFile in arrRegistryPolFiles
    if (CreateObject("Scripting.FileSystemObject").FileExists(POLFile)) then

        if(Join(ReadBinaryData(POLFile,3),"") = "8082101103") then
            wscript.echo True & vbtab & POLFile
        else
            wscript.echo False & vbtab & POLFile
        end if
    end if
next

wscript.quit

Function ReadBinaryData(Required_File,Int_Byte_Count)
' https://docs.microsoft.com/en-us/sql/ado/reference/ado-api/stream-object-ado
' https://docs.microsoft.com/en-us/sql/ado/reference/ado-api/stream-object-properties-methods-and-events

Const adTypeBinary = 1

' Requires Read/Write, otherwise it fails with Operation is not allowed in this context.
Const adModeReadWrite = 3

Dim arrByteArray : arrByteArray = Array(-1)
With CreateObject("ADODB.Stream")
    .Mode = adModeReadWrite
    .Type = adTypeBinary
    .Open
    .LoadFromFile Required_File

    Dim i
    For i=0 To Int_Byte_Count
        arrByteArray(i) = AscB(.Read(1))
        ReDim Preserve arrByteArray(UBound(arrByteArray)+1)
    Next
    .Close
End With

ReadBinaryData = arrByteArray
end function

After testing this out on our known good and known bad registry.pol files, we cast our rod (the code) into a special script all machines run to see if we’d catch any fish and sure enough we did!

From there it was just a matter of deciding how to handle the bad files: Alert IT for manual remediation or fix it during execution.

Here’s something ‘odd’ to me: When I convert the bits 80, 82, 101, 103 into HEX, I get 50, 52, 65, 67 which is the reverse of 67655250. Does anyone know why that is? I think it has something to do with Intel processors being ‘Little Endian’, which results in the bytes in memory not appearing in the same order as they do when fetched into a register but I don’t know; That’s a more than little beyond me!

In any event, hopefully someone will find this as useful as we did!

Good Providence to you!

8 comments

    1. I fixed the web conversion issues with the code so you can now copy & paste the vbscript code into a text file and save it as .vbs.

      Like

  1. This is great, thank you for sharing!

    While I love to see elegant solutions like this, curious if you have ever detected the root cause of the corruption?

    Over the years, the root causes with MS have never been addressed as far as I can tell. They have rightfully included it as a mandatory exclusion for ALL domain-joined Windows machines that run any AV product for that reason.

    My suspicions lay with the product that ironically is impacted the most – which is SCCM agent. Natively, its designed to inject certain client setting configurations directly as local policies (although I’m not 100% on its method or how it carries it out – would be easy to check with procmon). Just speculation as I have no hard evidence yet.

    Anyways, thanks again!

    Like

    1. Great question and unfortunately we never got to the bottom of it. At the time we had about 3k machines in the organization and at any given time there were maybe 1%-3% machines with this issue. Things seemed to get better over time but it never went away. We setup a configuration baseline in MECM/SCCM to fix this as it found machines with it.

      Like

Leave a comment