Practical engineering guidance
SYSVOL stopped replicating: dirty shutdowns and content freshness
Group Policy applies differently depending on which domain controller a client used. A read-only health check for DFSR SYSVOL, and the one recovery step that is safe to automate.
Series: Active Directory
A Group Policy change works for some users and not others. A logon script runs on Monday and not on Tuesday. A newly created policy applies in one site and is simply absent in another.
The common cause is not Group Policy. It is SYSVOL: the domain controllers no longer hold the same content, so what a client gets depends on which one it authenticated against. That makes the symptom intermittent, user-specific and, from the service desk’s point of view, unreproducible.
The problem: DFSR stops on purpose, and stopping is the safe choice
DFS Replication protects the domain from propagating bad data. Two of its safety mechanisms stop replication deliberately, and both look like a failure:
Dirty shutdown. If the DFSR database was not closed cleanly — an unexpected reboot, a service killed during shutdown, a rolled-back virtual machine snapshot — DFSR detects it on start. Event 2212 records the unexpected shutdown and the start of recovery. On Windows Server 2012 and later, event 2213 says recovery has been paused awaiting an administrative decision. Nothing replicates on that volume until somebody resumes it. Event 2214 confirms recovery finished.
Content freshness. If a domain controller has not replicated for longer than
MaxOfflineTimeInDays — 60 days by default — DFSR refuses to replicate at all and logs event
4012. This is protection against reanimating deleted content from a server that has been
offline too long, and it is very commonly seen on a DC that was powered off “temporarily”.
The worse case is event 2104: DFSR failed to recover its database on the volume and has stopped replication for every replicated folder on it.
A note on why this recurs
Two documented root causes are worth knowing before you fix a dirty shutdown for the third time. A DFSR service that is killed during shutdown because the service control manager’s timeout expired will produce this cycle repeatedly on a busy server. And restoring a virtual machine snapshot of a DFSR member is documented as a cause of self-perpetuating database corruption — the reason “just roll back the snapshot” is the wrong instinct for a domain controller.
The script
Read-only by default. With -ResumeReplication it will perform the one documented recovery
step that is safe to automate, and only with an explicit acknowledgement that a backup exists.
<#
.SYNOPSIS
Reports DFSR SYSVOL replication health on a domain controller.
.DESCRIPTION
Read-only by default. Checks the SYSVOL and NETLOGON shares, runs the sysvolcheck and
advertising directory diagnostics, reports the DFSR events that stop replication
(dirty shutdown, content freshness, database recovery failure), reads the content
freshness threshold and reports replication state and backlog.
With -ResumeReplication it calls the documented ResumeReplication method on volumes
that logged a paused dirty-shutdown recovery. Microsoft requires a backup of the
replicated content first, because conflict resolution during recovery can lose data,
so the script also requires -BackupConfirmed. Supports -WhatIf.
This script never performs an authoritative (D4) or non-authoritative (D2) SYSVOL
restore. Those are disruptive, domain-wide and documented as capable of data loss;
they are a planned change, not a script.
Run on a domain controller, elevated.
.PARAMETER EventDays
How far back to read the DFS Replication log. Default 30.
.PARAMETER PartnerComputerName
Optional replication partner for a backlog count. Defaults to no backlog check.
.PARAMETER ResumeReplication
Resume replication on volumes paused after a dirty shutdown. Requires -BackupConfirmed.
.PARAMETER BackupConfirmed
Your confirmation that the replicated content on this server is backed up.
.EXAMPLE
.\Get-SysvolReplicationHealth.ps1 -Verbose
.EXAMPLE
.\Get-SysvolReplicationHealth.ps1 -ResumeReplication -BackupConfirmed -WhatIf
.NOTES
Read-only unless -ResumeReplication is supplied.
Event 4012 (content freshness) is NOT resolved by this script: a domain controller that
has been offline beyond the threshold needs a decision about whether its content should
be trusted at all.
#>
#requires -Version 5.1
#requires -RunAsAdministrator
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param(
[ValidateRange(1, 365)]
[int]$EventDays = 30,
[string]$PartnerComputerName,
[switch]$ResumeReplication,
[switch]$BackupConfirmed
)
$ErrorActionPreference = 'Stop'
if ($ResumeReplication -and -not $BackupConfirmed) {
throw 'Refusing to resume replication without -BackupConfirmed. Microsoft requires a backup of the replicated folders first: conflict resolution during recovery can lose data.'
}
# --- Shares -----------------------------------------------------------------------------
$shares = foreach ($name in 'SYSVOL', 'NETLOGON') {
$share = Get-SmbShare -Name $name -ErrorAction SilentlyContinue
[pscustomobject]@{
Share = $name
Present = [bool]$share
Path = $share.Path
}
}
foreach ($share in $shares | Where-Object { -not $_.Present }) {
Write-Warning "$($share.Share) is not shared on this domain controller."
}
# --- Directory diagnostics --------------------------------------------------------------
# sysvolcheck reads the Netlogon SysVolReady state; advertising confirms the DC advertises
# its roles. Both are read-only tests.
$dcdiag = & dcdiag.exe /test:sysvolcheck /test:advertising 2>&1
$dcdiagPassed = ($dcdiag | Select-String -Pattern 'passed test (SysVolCheck|Advertising)').Count -eq 2
if (-not $dcdiagPassed) { Write-Warning 'dcdiag sysvolcheck or advertising did not pass. Review the full output in the result object.' }
# --- The events that stop replication ---------------------------------------------------
$stopEvents = @{
2212 = 'Unexpected shutdown detected; automatic recovery started.'
2213 = 'Recovery PAUSED after a dirty shutdown. Replication is stopped on this volume until resumed.'
2214 = 'Dirty shutdown recovery completed.'
2104 = 'DFSR could not recover its database. Replication stopped for all replicated folders on the volume.'
4012 = 'Content freshness protection stopped replication: this server was offline longer than MaxOfflineTimeInDays.'
4114 = 'SYSVOL replication membership disabled (expected during a D2/D4 procedure).'
4602 = 'SYSVOL initialised as authoritative (D4 completed).'
4604 = 'SYSVOL initialisation complete.'
4614 = 'SYSVOL waiting for initial replication.'
}
$events = [System.Collections.Generic.List[object]]::new()
try {
Get-WinEvent -FilterHashtable @{
LogName = 'DFS Replication'
Id = $stopEvents.Keys
StartTime = (Get-Date).AddDays(-$EventDays)
} -ErrorAction Stop | ForEach-Object {
$events.Add([pscustomobject]@{
TimeCreated = $_.TimeCreated
Id = $_.Id
Level = $_.LevelDisplayName
Meaning = $stopEvents[$_.Id]
Message = ($_.Message -split "`r?`n")[0]
RawMessage = $_.Message
})
}
}
catch {
Write-Verbose "No matching DFS Replication events in the last $EventDays days."
}
foreach ($id in 2213, 2104, 4012) {
$matching = @($events | Where-Object Id -EQ $id)
if ($matching.Count -gt 0) {
Write-Warning "Event $id present ($($matching.Count) occurrence(s)): $($stopEvents[$id])"
}
}
# --- Content freshness threshold and replication state ----------------------------------
$maxOfflineDays = try {
(Get-CimInstance -Namespace 'root\microsoftdfs' -ClassName DfsrMachineConfig -ErrorAction Stop).MaxOfflineTimeInDays
}
catch {
Write-Warning "Could not read DfsrMachineConfig: $($_.Exception.Message)"
$null
}
$replicationState = try { Get-DfsrState -ComputerName $env:COMPUTERNAME -ErrorAction Stop | Select-Object -First 20 }
catch { Write-Verbose "Get-DfsrState unavailable: $($_.Exception.Message)"; $null }
$backlog = $null
if ($PartnerComputerName) {
$backlog = try {
# The SYSVOL replication group is hidden from the DFSR cmdlets unless asked for.
Get-DfsrBacklog -GroupName 'Domain System Volume' -FolderName 'SYSVOL Share' `
-SourceComputerName $PartnerComputerName -DestinationComputerName $env:COMPUTERNAME `
-ErrorAction Stop -Verbose 4>&1 | Select-Object -First 5
}
catch {
Write-Warning "Backlog check against $PartnerComputerName failed: $($_.Exception.Message)"
$null
}
}
# --- Optional recovery: resume a paused dirty-shutdown volume ---------------------------
$resumed = [System.Collections.Generic.List[object]]::new()
if ($ResumeReplication) {
$paused = @($events | Where-Object Id -EQ 2213)
if ($paused.Count -eq 0) {
Write-Verbose 'No paused dirty-shutdown recovery found; nothing to resume.'
}
else {
# The volume is identified in the event text; match it against the configured volumes
# rather than resuming everything indiscriminately.
$guidPattern = '[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}'
$volumeGuids = $paused | ForEach-Object {
([regex]::Matches($_.RawMessage, $guidPattern) | ForEach-Object { $_.Value })
} | Select-Object -Unique
if (-not $volumeGuids) {
Write-Warning 'Event 2213 found but no volume identifier could be read from it. Resume manually after reading the event.'
}
foreach ($guid in $volumeGuids) {
$volume = Get-CimInstance -Namespace 'root\microsoftdfs' -ClassName DfsrVolumeConfig -ErrorAction Stop |
Where-Object { $_.VolumeGuid -like "*$($guid.Trim('{}'))*" }
if (-not $volume) {
Write-Warning "No DFSR volume configuration matches $guid."
continue
}
$label = if ($volume.VolumePath) { $volume.VolumePath } else { $guid }
if ($PSCmdlet.ShouldProcess("DFSR volume $label", 'Resume replication after dirty shutdown')) {
try {
$null = Invoke-CimMethod -InputObject $volume -MethodName ResumeReplication -ErrorAction Stop
$resumed.Add([pscustomobject]@{ Volume = $guid; Result = 'resume requested' })
Write-Verbose "Resume requested for volume $guid."
}
catch {
Write-Warning "Resume failed for $guid : $($_.Exception.Message)"
$resumed.Add([pscustomobject]@{ Volume = $guid; Result = "failed: $($_.Exception.Message)" })
}
}
}
}
}
[pscustomobject]@{
DomainController = $env:COMPUTERNAME
CollectedUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm')
Shares = $shares
DcdiagPassed = $dcdiagPassed
DcdiagOutput = $dcdiag
MaxOfflineTimeDays = $maxOfflineDays
StopEvents = $events | Sort-Object TimeCreated -Descending | Select-Object TimeCreated, Id, Level, Meaning, Message
ReplicationState = $replicationState
Backlog = $backlog
Resumed = $resumed
}
What to do with each finding
Event 2213, replication paused. This is the case the script can resolve. Back up the replicated content first — Microsoft is explicit that conflict resolution during recovery can lose data — then resume. If the same server does this repeatedly, treat the recurrence as the real fault and look at the service shutdown timeout, or at whether somebody is rolling back snapshots of a domain controller.
Event 4012, content freshness. Do not simply raise MaxOfflineTimeInDays to make it go
away. The protection is telling you this domain controller’s copy of SYSVOL is too old to be
trusted. The decision is whether to bring its content back in line from a healthy partner — a
non-authoritative synchronisation — or to demote and rebuild the server, which is often the
better answer for a DC that has been offline for two months.
Event 2104, database recovery failed. Beyond the scope of a script. Microsoft explicitly discourages deleting the DFSR database to force a rebuild.
Shares missing, dcdiag failing, but no DFSR stop events. Look at whether SYSVOL ever initialised: events 4614 and 4604 bracket initial replication, and a DC stuck waiting for initial replication never shares SYSVOL at all.
Why D4 and D2 are not in the script
The authoritative and non-authoritative restore procedures work by setting msDFSR-Enabled
and msDFSR-Options on the DFSR subscription object of each domain controller, in a specific
order, with the DFSR service stopped domain-wide for an authoritative restore.
They are documented, they work, and they are the correct answer to a genuinely divergent SYSVOL. They are also capable of losing Group Policy content across the domain if the wrong server is chosen as authoritative, and non-authoritative members discard their unreplicated content into a preserved folder that later syncs purge.
That is a change with a maintenance window, a backup and a rollback plan — not a switch on a script somebody found in an article. The script reports the state that tells you whether you need it.
Verification and limits
The DFS Replication log channel, events 2212, 2213, 2214, 2104, 4012, 4114, 4602, 4604 and
4614, the DfsrVolumeConfig class and its ResumeReplication method with Microsoft’s
backup-first requirement, MaxOfflineTimeInDays on DfsrMachineConfig with its 60-day
default, the cmdlets Get-DfsrState, Get-DfsrBacklog and Get-DfsReplicationGroup, the
dcdiag test names sysvolcheck and advertising, the D4/D2 attributes and procedure, the
service shutdown timeout root cause and the snapshot-rollback warning were checked against
current Microsoft documentation on 20 September 2026.
The script was written for this article and statically analysed. It was not executed
against a production domain controller, and the -ResumeReplication path in particular should
be exercised with -WhatIf in a lab before you trust it during an incident.
I could not confirm the meaning of DFSR event 5002 on Microsoft Learn, so it is absent from the
table rather than guessed at, and I have not verified the exact command-line syntax of
dfsrdiag backlog; the script uses the PowerShell cmdlet, whose parameters are documented.
References
- DFSR event 2213: resume replication after a dirty shutdown
- DFSR event 2212 after restarting a server
- Troubleshoot missing SYSVOL and NETLOGON shares
- Force authoritative and non-authoritative synchronisation for DFSR-replicated SYSVOL
- Get-DfsrBacklog
- DFS Replication does not replicate files after restoring a snapshot
Reader feedback
Was this article useful?
No ratings yet. Be the first to rate this article.
