This was another article that peaked my interest, when you save a group policy object on a specific domain controller everybody just assumes that replication will take care of replicating settings to all the other domain controllers.
Assumption, unfortunately does seem to be the foundation for bad decisions.
The modified time is not replicated?
When you make a Group Policy change on one domain controller, the actual policy changes will replicate to other DCs, but the "modified time" attribute itself doesn't replicate. This is normal behavior. The modified timestamp reflects when the change was made on that specific DC.
So if you're seeing different modified times across your DCs for the same Group Policy Object (GPO), that's completely expected behavior and not a sign of a replication issue. The policy content itself should be identical across all DCs, just with different timestamps showing when it was replicated to each one.
Using the modified time will generate false positive?
If you therefore have a script that monitors the modified time you will get a large number of false positives because each main controller will have a different modified time that is not replicated.
If you want to script GPO health monitoring I would recommend that we:
- Compare the actual GPO version numbers
- Check the GPO content/settings directly
- Monitor SYSVOL replication state
Monitor the right attribute's
I have therefore decided to use the first option, because to avoid false positives the requirements for the script are as below:
GPOs use two types of version numbers we should be comparing instead of ModificationTime:
User Version Number
Computer Version Number
These version numbers increment whenever a change is made to the respective portion of the GPO.
Scripting
Now is the time to get the scripting and view the final outcome of the scripting as well, when run this is what the script looks like, notice is this example we see "No replication issue found, All GPOs are in sync."
Script : GPOReplicationCheck.ps1
# GPO Replication Health Monitor
# This script checks GPO replication across domain controllers and performs health checks
# Function to write to both console and log file
function Write-OutputAndLog {
param(
[string]$Message,
[string]$LogPath,
[string]$Type = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "[$timestamp] [$Type] $Message"
Write-Host $logMessage
Add-Content -Path $LogPath -Value $logMessage
}
# Set up logging in current directory
$logFile = "GPOHealth_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
try {
Write-OutputAndLog -Message "Starting GPO replication health check" -LogPath $logFile
# Get all domain controllers
$dcs = Get-ADDomainController -Filter *
Write-OutputAndLog -Message "Found $($dcs.Count) domain controllers" -LogPath $logFile
# Get PDC Emulator
$pdc = Get-ADDomainController -Discover -Service PrimaryDC
Write-OutputAndLog -Message "PDC Emulator is $($pdc.HostName)" -LogPath $logFile
# Get all GPOs
$gpos = Get-GPO -All
Write-OutputAndLog -Message "Found $($gpos.Count) Group Policy Objects" -LogPath $logFile
# Store GPO versions from each DC
$gpoVersions = @{}
$discrepancies = @{}
foreach ($dc in $dcs) {
Write-OutputAndLog -Message "Checking GPOs on $($dc.HostName)" -LogPath $logFile
try {
$gpoVersions[$dc.HostName] = @{}
foreach ($gpo in $gpos) {
$gpoVersion = Get-GPO -Guid $gpo.Id -Server $dc.HostName -ErrorAction Stop
$gpoVersions[$dc.HostName][$gpo.Id] = @{
'UserVersion' = $gpoVersion.User.DSVersion
'ComputerVersion' = $gpoVersion.Computer.DSVersion
}
}
}
catch {
Write-OutputAndLog -Message "Error accessing $($dc.HostName): $_" -LogPath $logFile -Type "ERROR"
}
}
# Compare versions and identify discrepancies
foreach ($gpo in $gpos) {
$userVersions = @{}
$computerVersions = @{}
foreach ($dc in $dcs) {
if ($gpoVersions[$dc.HostName].ContainsKey($gpo.Id)) {
$userVersions[$dc.HostName] = $gpoVersions[$dc.HostName][$gpo.Id].UserVersion
$computerVersions[$dc.HostName] = $gpoVersions[$dc.HostName][$gpo.Id].ComputerVersion
}
}
$uniqueUserVersions = $userVersions.Values | Select-Object -Unique
$uniqueComputerVersions = $computerVersions.Values | Select-Object -Unique
if ($uniqueUserVersions.Count -gt 1 -or $uniqueComputerVersions.Count -gt 1) {
Write-OutputAndLog -Message "Discrepancy found for GPO: $($gpo.DisplayName) ($($gpo.Id))" -LogPath $logFile -Type "WARNING"
# Record discrepancies
foreach ($dc in $dcs) {
$pdcUserVersion = $gpoVersions[$pdc.HostName][$gpo.Id].UserVersion
$pdcComputerVersion = $gpoVersions[$pdc.HostName][$gpo.Id].ComputerVersion
$dcUserVersion = $gpoVersions[$dc.HostName][$gpo.Id].UserVersion
$dcComputerVersion = $gpoVersions[$dc.HostName][$gpo.Id].ComputerVersion
if ($dcUserVersion -ne $pdcUserVersion -or $dcComputerVersion -ne $pdcComputerVersion)
{
if (-not $discrepancies.ContainsKey($dc.HostName)) {
$discrepancies[$dc.HostName] = @()
}
$discrepancies[$dc.HostName] += "$($gpo.DisplayName) (User: $dcUserVersion vs $pdcUserVersion, Computer: $dcComputerVersion vs $pdcComputerVersion)"
}
}
}
}
# Generate advisory actions
Write-OutputAndLog -Message "`nAdvisory Actions:" -LogPath $logFile
if ($discrepancies.Count -eq 0) {
Write-OutputAndLog -Message "No replication issues found. All GPOs are in sync." -LogPath $logFile
}
else {
foreach ($dc in $discrepancies.Keys) {
Write-OutputAndLog -Message "Domain Controller $dc has GPO version discrepancies:" -LogPath $logFile
Write-OutputAndLog -Message "Affected GPOs:" -LogPath $logFile
foreach ($gpoInfo in $discrepancies[$dc]) {
Write-OutputAndLog -Message "- $gpoInfo" -LogPath $logFile
}
Write-OutputAndLog -Message "`nRecommended Actions:" -LogPath $logFile
Write-OutputAndLog -Message "1. Force AD replication across all domain controllers:" -LogPath $logFile
Write-OutputAndLog -Message " repadmin /syncall /AdeP" -LogPath $logFile
Write-OutputAndLog -Message "2. Verify replication health:" -LogPath $logFile
Write-OutputAndLog -Message " dcdiag /test:replications" -LogPath $logFile
Write-OutputAndLog -Message "3. Check Event Viewer for potential replication errors" -LogPath $logFile
Write-OutputAndLog -Message "4. Verify network connectivity to other domain controllers" -LogPath $logFile
Write-OutputAndLog -Message "5. Check DNS settings and name resolution" -LogPath $logFile
}
}
}
Write-OutputAndLog -Message "Critical error in script execution: $_" -LogPath $logFile -Type "ERROR"
}
finally {
Write-OutputAndLog -Message "Script execution completed. Log file: $logFile" -LogPath $logFile
}