VBScript

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 Explici
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!

Non Sequitur: Creating Dynamic Variables on the Fly in VBScript

Update 2016-06-08: WordPress keeps butchering the code so I’m removing the pretty formatting for now.

I’m only mentioning this because of something that came up recently and I thought it was kind of neat.

To keep things simple, I’m going to break this up into a few posts:

  1. Batch
  2. VBScript
  3. PowerShell

 

VBScript

About 4 if not almost 5 years ago, before I learned the ways of PowerShell, I moved up from Batch to VBScript mostly when things got complicated.  I was still getting used to PowerShell, practicing on doing simple things like managing AD objects and doing things in Exchange (mail exports, delegation, tracking down messages etc.) so I relied heavily on Batch and VBScript to get things done because they were languages I was quite comfortable with.

SCCM 2012 (no R2) is my first foray into the System Center ecosystem and I was excuted as it was supposed to solve many of the problems we faced with the imaging process we had then, and address nearly all of our concerns with some of changes we would be making in a year’s time or so.  Like most organizations we have a hefty list of applications on all of our workstations that usually fall into one of the following categories:

  1. Core Apps – These are applications required by just about everyone in the organization like Microsoft Office, FileSite, EMM etc.
  2. Practice Apps – These are applications required by those in a particular practice group (IP, Litigation, Trust & Estates etc.) or a functional group (Development, Admin/HR, Accounting etc.)
  3. Extra Apps – These are usually applications that aren’t necessarily required but are often needed.

When creating the Task Sequence, we had two options for installing applications:

  1. Add the applications to the installation list, or
  2. Install applications according to a dynamic variable list.

The former is great because it allows a friendly interface for selecting, adding and organizing the applications for installation. The biggest drawback here is that each task can only support 9 application installations, which means more install application tasks and a lot of clicking.

The latter is also great because one can define Collection Variables that correspond to applications and install them on the fly.  Its biggest drawback was that it required maintaining a list of Collection Variables in potentially multiple collections and I didn’t want to spend my time renumbering because I removed or added an application since there couldn’t be any gaps in the numbers.  Augh.

Please don’t misconstrue: Both methods work and I’m not trying to knock either method!

Note: It’s possible things have changed since then and maybe there was in fact a solution addressing this exact problem.  I wasn’t an expert then – nor am I an expert now – so I improvised.

I thought to myself “Wouldn’t it neat if I could generate sequentially numbered variables on the fly at runtime?”  Then it dawned on me: my old batch script!  I decided to write a small vbscript that I could use to populate those variables in the TS.

I ended up with something very similar to what’s below.  It was a ‘proof-of-concept’ script running in my, then, lab environment and it worked wonderfully straight through to production.

option explicit

' Lets define our array housing the core applications.
' Easy to add, delete & reorganize.
'
' NOTE: The application names here corespond to the application
' names within SCCM 2012
dim arrCoreApps : arrCoreApps=Array("Microsoft Office Professional Plus 2099",_
				    "FileSite",_
				    "DeskSite",_
				    "Nuance PDF Converter Enterprise",_
				    "Workshare Professional",_
				    "PayneGroup Metadata Assistant",_
				    "PayneGroup Numbering Assistant",_
				    "Adobe Acrobat Pro",_
				    "Some Fake App",_
				    "Some Real App"_
				    )

' With the array populated, lets build sequentially numbered variables with a static prefix
' The prefix here is CoreApps
' This is variable that should be used in the Application Install Task Sequence step.

BuildApplicationVariables arrCoreApps,"CoreApps"

' Lets make sure the Task Sequence Variables have successfully been defined
RetrieveApplicationVariables arrCoreApps,"CoreApps"
wscript.echo

' Another example
dim arrExtraApps : arrExtraApps=Array("Java",_
				      "Google Chrome",_
				      "Mozilla Firefox"_
				      )

BuildApplicationVariables arrExtraApps,"ExtraApps"
RetrieveApplicationVariables arrExtraApps,"ExtraApps"

Sub BuildApplicationVariables(Required_Application_Array,Required_Prefix)
	dim arrApplications : arrApplications = Required_Application_Array
	dim sPrefix : sPrefix = Required_Prefix

	dim i
	For i=0 to UBound(arrApplications)
		if (i+1 < 10) Then
			Execute "wscript.echo ""Creating Variable ["" & sPrefix & 0 & i+1 & ""] Containing ["" & arrApplications(i) & ""]"""
			ExecuteGlobal sPrefix & "0" & i+1 & "=" & """" & arrApplications(i) & """"
		else
			Execute "wscript.echo ""Creating Variable [" & sPrefix & i+1 & "] Containing [" & arrApplications(i) & "]"""
			ExecuteGlobal sPrefix & i+1 & "=" & """" & arrApplications(i) & """"
		end if
	Next
End Sub

Sub RetrieveApplicationVariables(Required_Application_Array,Required_Prefix)
	dim arrApplications : arrApplications = Required_Application_Array
	dim sPrefix : sPrefix = Required_Prefix

	dim i
	For i=0 to UBound(arrApplications)
		if (i+1 < 10) Then
			Execute "wscript.echo vbtab & ""[" & sPrefix & "0" & i+1 & "] = ["" & " & sPrefix & "0" & i+1 & " & ""]"""
		else
			Execute "wscript.echo vbtab & ""[" & sPrefix & i+1 & "] = ["" & " & sPrefix & i+1 & " & ""]"""
		end if
	Next
end Sub

Anyway this worked well for us in setting up those dynamic variables on the fly and it’s a piece of code I’ll keep in my back pocket just in case.

Good Providence!