Posts Tagged ‘Group Policy’

Introduction

Last week I came across a client-side issue applying Group Policies. It turned out to be a corrupt local group policy database file. Here’s how I went about troubleshooting and fixing the issue and then automating detection and resolution of the problem using PowerShell.

Troubleshooting

The Resultant Set of Policy (RSoP) indicated a problem processing the policy settings and advised to look in the Application event log for more details, and quite helpfully showed the exact time of the expected event to look for.

Looking at the Application event log, there was a warning event at the expected time with event ID 1202, with this description:

"Security policies were propagated with warning. 0x4b8 : An extended error has occurred."

The event description suggests searching http://support.microsoft.com which turned up this KB article:

http://support.microsoft.com/kb/324383 – Troubleshooting SCECLI 1202 Events

Scrolling down to Win32 error code for Event ID 1202 "0x4b8: An extended error has occurred", the article tell us how to enable detailed debugging to help troubleshoot the problem. It also points to this KB article:

http://support.microsoft.com/kb/278316/ – ESENT Event IDs 1000, 1202, 412, and 454 Are Logged Repeatedly in the Application Event Log

And there we see Message 2 – the exact error from our event log.

Resolution

The problem is that the local Group Policy database is corrupt. To fix it, we will need to re-create the local security database. This procedure is described in the article. The article doesn’t tell us what might have caused the problem in the first place. There could be any number of causes and finding one is going to take a bit more investigative work, but for now we’ll just worry about fixing the problem.

After re-creating the security database from template, per the instructions, I ran the gpupdate /force command. Hey presto! Group policy now applies correctly. This Application event log entry confirms it – Event ID 1704:

Automation

I discovered that this problem was more widespread than just one machine, so I set about looking to automate detection and resolution of the problem. Thankfully secedit.exe provides command line switches to do this:

http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/secedit_cmds.mspx?mfr=true

I decided to use this in a PowerShell script. The first thing to do was to find a way to detect whether the problem exists. Using Get-EventLog, I queried for the exact 1202 event and checked if any 1704 events had occurred since the 1202 event. If a 1704 event has occurred since the 1202 warning, I ignore it, because Group Policy has applied successfully.

Next, the script takes a backup of the secedit.sdb for rollback and proceeds to re-create the local group policy database file using the secedit command line.

In my tests, secedit.exe returned an error code 3, although the task appeared to complete successfully and the issue was resolved. I checked the secedit log file and found this entry at the end of the file:

"Task is completed. Some files in the configuration are not found on this system so security cannot be set/queried. It’s ok to ignore."

I can only assume that this is the reason for error code 3 being returned. The script deems error level 3, combined with this entry in the log to be a successful re-creation of the database file, otherwise it rolls back to the original secedit.sdb file.

I have provided the code below, which needs to be run locally on the machine. The script has only been tested on Windows XP. You could deploy this out via Group Policy, but it won’t be much good if the computers you are trying to target aren’t applying Group Policies! So, you’ll have to use a software distribution tool, psexec, or some other mechanism.

Warning: Re-creating the local security database file can reset permissions on a whole bunch of files and registry keys, so do this at your own peril.

#*=============================================
#* FUNCTION LISTINGS
#*=============================================

Function Exit-Script {
	Param($exitCode)
	Write-Log "Exiting Script."
	Exit $exitCode
}

# Function to write output to the host and to log file
Function Write-Log {
	[CmdletBinding(DefaultParametersetName="LogEntry")] 
    Param(
	[Parameter(ParameterSetName="LogEntry", Position = 0, Mandatory = $true, ValueFromPipeline = $true)][ValidateNotNullOrEmpty()]
    [String]$Message,
    [Parameter(Position = 1)]
    [String]$LogFile = "$env:temp\$(Split-Path $MyInvocation.ScriptName -Leaf).log",
	[Parameter(ParameterSetName="LogEntry", Position = 2)] [ValidateSet("Error", "Warning")]
	[string]$Type = "",	
	[Parameter(ParameterSetName="NewLine", Position=0)] 
	[switch]$NewLine	
    )		
	
	switch ($PsCmdlet.ParameterSetName) 
    { 
	    "NewLine" 	{ Write-Host ""; "" | Out-File -FilePath $LogFile -Append }
	    "LogEntry" 	{			
			
			$date = Get-Date -Format "yyyy-MM-dd HH:mm:ss"	
			$psCallStack = Get-PSCallStack
			
			# Get the function name from where the Write-Log function was called
			If ($psCallStack[1].command -notmatch ".") { $function = $psCallStack[1].command } Else { $function = "Script" } 
			
			$logColumns = @("$date","$function","$($Type.ToUpper())","$message")
			[string]$logMessage = ""	
			
			# Build the message string to insert colon only where needed
			Foreach ($logColumn in $logColumns) { 
				If ($logColumn -ne "") {
					If ($logMessage -eq "" ) { 
						$logMessage = $logColumn
					}
					Else { 
						$logMessage += [string]::join("$logMessage"," : $logColumn")
					}
				}							
			}

			# Write message to the screen
			Switch ($type) {
				"Error" 	{ $logMessage += ": Line $($MyInvocation.ScriptLineNumber)"; Write-Host $logMessage -ForegroundColor Red }
				"Warning" 	{ Write-Host $logMessage -ForegroundColor Yellow }
				Default		{ Write-Host $logMessage }				
			}
			
			# Write message to the log file
		    Write-Output $logMessage | Out-File -FilePath $LogFile -Append
		}
	}
} #end function

# Function to start a CLI application and return the exit code
Function Start-CliApplication { 
    param ( [string]$application, [string]$arguments )
	
    # Build Startinfo and set options according to parameters
	$startInfo = new-object System.Diagnostics.ProcessStartInfo 
   	$startInfo.FileName = $application
	$startInfo.Arguments = $arguments
	$startInfo.WindowStyle = "Hidden"
	$startInfo.CreateNoWindow = $true
	$startInfo.UseShellExecute = $false
    	
	# Start the process
	$process = [System.Diagnostics.Process]::Start($startinfo)

	# Wait until the process finished
	Do {
 		If( -not $process.HasExited ) {
  			$process.Refresh()
		}
	} While( -not $process.WaitForExit(1000) )
	
	# Output the exitcode
	Write $process.exitcode
}

Function Backup-SecDB {
	If (Test-Path (Join-Path $env:SystemRoot "Security\Database\secedit.sdb")) {
		Write-Log "Backing up Security Database..."
		Rename-Item -Path (Join-Path $env:SystemRoot "Security\Database\secedit.sdb") -NewName "secedit.old" -Force
	}
}

Function Restore-SecDB {
	If (Test-Path (Join-Path $env:SystemRoot "Security\Database\secedit.old")) {
		Write-Log "Restoring Old Security Database..."
		Rename-Item -Path (Join-Path $env:SystemRoot "Security\Database\secedit.old") -NewName "secedit.sdb" -Force
	}
}

Function Reset-SecurityDatabase {
	# Reset the security database from template
	Write-Log "Regenerating Security Database..."
	$RegenerationResult = Start-CliApplication -Application "SECEDIT.EXE" -Arguments "/CONFIGURE /CFG `"$env:systemroot\Security\Templates\setup security.inf`" /DB `"$env:temp\secedit.sdb`" /OVERWRITE /LOG `"$env:temp\SeceditRegeneration.log`" /QUIET"
	If ($RegenerationResult -eq 0) {
        Write-Log "Regeneration of Security Database Successful" 
    }
    ElseIf ($RegenerationResult -eq 3) {
        If (Test-Path (Join-Path $env:temp "\SeceditRegeneration.log")) { 
            $seceditRegenerationLog = Get-Content (Join-Path $env:temp "\SeceditRegeneration.log")
            If ($seceditRegenerationLog -match "It's ok to ignore") {
                 Write-Log "Regeneration of Security Database Successful."
            }
            Else { Write-Log "Regeneration of Security Database Failed." -Type Error; Restore-SecDB; Exit-Script 1 } 
        }
        Else { Write-Log "Regeneration of Security Database Failed." -Type Error; Restore-SecDB; Exit-Script 1 } 
   	}
   	Else { Write-Log "Regeneration of Security Database Failed." -Type Error; Restore-SecDB; Exit-Script 1 } 

	Write-Log "Replacing Security Database..."
	Move-Item -Path (Join-Path $env:Temp "secedit.sdb") -Destination (Join-Path $env:SystemRoot Security\Database) -Force
}

Function Update-GroupPolicy {
	# Update Group Policy
	Write-Log "Updating Group Policy..."
	$gpupdateResult = Start-CliApplication "gpupdate" "/force"
	If ($gpUpdateResult -eq 0) {
		Write-Log "Group Policy Update Successful"
	}
	Else { Write-Log "Group Policy Update Failed" -Type Error; Exit-Script 1}
}

#*=============================================
#* END FUNCTION LISTINGS
#*=============================================

#*=============================================
#* SCRIPT BODY
#*=============================================

Write-Log "Searching Event Log for Security Database errors..."
$eventSceCliErrors = Get-EventLog Application -Newest 500 | Where { $_.eventID -eq "1202" -and $_.source -eq "SceCli" -and $_.message -match "0x4b8" } | Sort-Object -Property TimeGenerated -Descending | Select-Object -First 1
$eventSceCliSuccess = Get-EventLog Application -Newest 500 | Where { $_.eventID -eq "1704" -and $_.source -eq "SceCli" -and $_.message -match "successfully" } | Sort-Object -Property TimeGenerated -Descending | Select-Object -First 1

# If group policy has not been applied successfully since the last SceCli extended error, initiate remediation action. 
If ($eventSceCliErrors.TimeGenerated -gt $eventSceCliSuccess.TimeGenerated) {
	Write-Log "Group Policy has not been applied successfully since the last SceCLI Errors detected in the Event Log. Initiating Remediation Action..." -Type Warning
	
	# Backup the security database file
	Backup-SecDB
	
	# Regenerate the local security database from template
	Reset-SecurityDatabase
	
	# Update Group Policy
	Update-GroupPolicy
}
ElseIf ($eventSceCliErrors) {
	Write-Log "SceCLI Errors detected in the Event Log but Group Policy has successfully applied since the errors were generated.`nNo action necessary."
}
Else {
	Write-Log "No SceCLI Errors detected in the Event Log."
}

Write-Log "Script Complete."
Exit-Script 0

#*=============================================
#* END SCRIPT BODY
#*=============================================


This code is provided "as is" and without warranties as to performance or merchantability. The author and/or distributors of this code may have made statements about this code. Any such statements do not constitute warranties and shall not be relied on by the user in deciding whether to use this code.

This code is provided without any express or implied warranties whatsoever. Because of the diversity of conditions and hardware under which this code may be used, no warranty of fitness for a particular purpose is offered. The user is advised to test the code thoroughly before relying on it. The user must assume the entire risk of using the code.

Advertisements