dfsrdiag backlog: why the count is not in the output

dfsrdiag is the operational and diagnostic command line tool that ships with DFS Replication, and its dfsrdiag backlog subcommand answers one question: how many file updates does one member still owe another. On a domain controller that question is really about sysvol, because sysvol replicates over DFSR, and a Group Policy edit that never leaves the domain controller that made it is a backlog nobody looked at.

The number matters more than the file names. A backlog of four on a Monday morning is ordinary latency; the same four still sitting there an hour later is not, and sixty thousand after a restore is a different problem again. Two tools report it, dfsrdiag backlog at a CMD prompt and Get-DfsrBacklog in PowerShell, and they do not agree about where the number lives.

dfsrdiag has no reference page on Microsoft Learn. It is absent from the Windows Commands A to Z index and its URL returns a 404. The PowerShell side is documented, but every DFSR reference page cited here carries the same ms.date of 12/20/2016, and the one-liner those pages hand you for reading the count returns a string, compares wrongly against a threshold, and reports only the first replicated folder it happens to see. What follows is five boundaries, each measured rather than asserted, and a Get-DfsrBacklog sweep that covers every direction.

Applies to: Windows Server 2019 / 2022 / 2025, the three monikers the DFSR reference pages are published under.


Quick answer

On a domain controller this is the pair that answers “is sysvol behind, and by how much”. The PowerShell form asks for the count in the verbose stream, because the object output stops at 100 files. The CMD form prints the count on its first line.

# Get-DfsrBacklog caps its object output at 100 files, so the count has to come out
# of the verbose stream. 4>&1 merges that stream into the output, which already holds
# file records, so keep only the verbose ones or the parse below hits a null Message.
$v = Get-DfsrBacklog -GroupName 'Domain System Volume' -FolderName 'SYSVOL Share' `
         -SourceComputerName DC01 -DestinationComputerName DC03 -Verbose 4>&1 |
     Where-Object { $_ -is [System.Management.Automation.VerboseRecord] }

# Anchor the parse to the end of the line so nothing in the folder name shifts the field.
foreach ($m in @($v.Message)) {
    if ($m -match 'Replicated folder: "(?<rf>[^"]+)"\. Count: (?<n>\d+)\s*$') {
        '{0,-16}{1}' -f $Matches['rf'], [int]$Matches['n']
    }
}

The CMD equivalent needs no parsing at all when you only want to read it yourself. Both names contain a space, so both are quoted.

rem Typed at a CMD prompt. dfsrdiag prints the count itself, above the file list.
dfsrdiag backlog /rgname:"Domain System Volume" /rfname:"SYSVOL Share" /sendingmember:DC01 /receivingmember:DC03
Note: A backlog is not automatically a fault. The Get-DfsrBacklog reference page says it plainly: “This is not necessarily an indication of problems. A backlog indicates latency, and a backlog may be expected in your environment”. Read the number twice, ten minutes apart, before deciding anything.

Before the first example

Four things have to be true before any command below returns a number. Each one gets its own block so you can run them in order.

1. An elevated prompt on a member of the replication group. Microsoft’s own sysvol procedure calls for an elevated command prompt, and the backlog query reads the same service.

rem A High Mandatory Level group in the token means the prompt is elevated.
whoami /groups | findstr /c:"S-1-16-12288"

rem The backlog query talks to this service. Stopped means every answer below is empty.
sc query DFSR

2. dfsrdiag.exe on the machine. The archived Microsoft post on slow DFSR replication states where it comes from: Dfsrdiag is installed when you install DFSR on the server. On a domain controller that is already true.

rem where resolves a name against PATH, so an empty result means the tool is absent.
where dfsrdiag
where dfsrmig

3. The replication group and replicated folder names. On a domain controller they are Domain System Volume and SYSVOL Share. The first is hidden from the cmdlet that lists groups unless you ask for it, which is Boundary 2 below.

# -IncludeSysvol is what makes this cmdlet return the group every DC already has.
Get-DfsReplicationGroup -GroupName * -IncludeSysvol | Format-Table GroupName, State

# Membership names the members, the folder and where its content actually sits on disk.
Get-DfsrMembership -GroupName 'Domain System Volume' |
    Format-Table ComputerName, FolderName, ContentPath, State

4. One helper function. Every example after this one calls it. It does three jobs that the documented one-liner does not: it keeps only the verbose records, it anchors the parse, and it turns the raw number into a bucket so a report can be compared against a baseline without every row differing on every run.

function Get-BacklogRow {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [string] $From,
        [Parameter(Mandatory)] [string] $To,
        [string] $GroupName  = 'Domain System Volume',
        [string] $FolderName = 'SYSVOL Share',
        [int]    $Threshold  = 100
    )

    # 4>&1 merges the verbose stream into the output stream, which already holds up to
    # 100 file records. Those records have no Message, so filter before parsing.
    $records = Get-DfsrBacklog -GroupName $GroupName -FolderName $FolderName `
                   -SourceComputerName $From -DestinationComputerName $To -Verbose 4>&1 |
               Where-Object { $_ -is [System.Management.Automation.VerboseRecord] }

    $rows = foreach ($m in @($records.Message)) {
        if ($m -match 'Replicated folder: "(?<rf>[^"]+)"\. Count: (?<n>\d+)\s*$') {
            $n = [int]$Matches['n']
            [pscustomobject]@{
                From   = $From
                To     = $To
                Folder = $Matches['rf']
                Backlog = $n
                State  = if ($n -eq 0) { 'clear' } elseif ($n -le $Threshold) { 'latency' } else { 'backlog' }
            }
        }
    }

    # No verbose record is not the same as no backlog, so say which one it was.
    if ($rows) { $rows }
    else {
        [pscustomobject]@{ From = $From; To = $To; Folder = '-'; Backlog = -1; State = 'no record' }
    }
}
Warning: Get-DfsrBacklog and dfsrdiag both run only on Windows, so no output in this article was captured from either of them. Every output block below is a measurement run: the same parsing, bucketing and comparison code, running on PowerShell 7.4.6 with a lab fixture in place of the service. Where the shape of real tool output matters, the article says which Microsoft page publishes it.

Example 1: ask one direction about one pair

The problem: a Group Policy change made on DC01 has not appeared on DC03, and the Group Policy side of the investigation has come up clean.

The solution: query the pair in the direction the change has to travel, and read the bucket rather than the number.

# From DC01 to DC03: what DC01 holds that DC03 has not applied yet. Swapping the two
# names is a different question, not a second opinion on the same one.
Get-BacklogRow -From DC01 -To DC03 | Format-Table -AutoSize

One row comes back per replicated folder, carrying Backlog and State. State is the column worth alerting on, because the raw number moves on a busy pair between one reading and the next and a threshold on it produces noise. A no record row means DFSR reported nothing for that direction, which is not the same statement as a count of zero.


Example 2: the count, not the first hundred files

The problem: a monitoring script alerts when the sysvol backlog goes over 500 files. It has never fired, including on the morning a domain controller came back from a restore tens of thousands of files behind.

The solution: the object output cannot reach 500. The reference page states the cap in one sentence: “The maximum number of files that this command displays is 100.” Its own example descriptions settle whether that cap sits on the display or on the objects, because each one says the command “retrieves the first 100 unreplicated changes”. Any threshold above 100 is unreachable through the objects, and the accurate number is in the verbose stream.

# Wrong: the object count saturates at 100, so this comparison can never be true.
(Get-DfsrBacklog -GroupName 'Domain System Volume' -FolderName 'SYSVOL Share' `
     -SourceComputerName DC01 -DestinationComputerName DC03).Count -gt 500

# Right: the helper reads the count out of the verbose stream, where no cap applies.
# The number is called Backlog and not Count, for the reason measured below.
(Get-BacklogRow -From DC01 -To DC03).Backlog -gt 500

The table below follows from that one documented sentence. Seven realistic backlog sizes, the number of objects each one can produce, and what a threshold of 500 concludes from each column.

      real   objects  .Count -gt 500 verbose n -gt 500
      ----   -------  -------------- -----------------
         0         0           False             False
         7         7           False             False
        99        99           False             False
       100       100           False             False
       101       100           False             False
      2400       100           False              True
     50000       100           False              True

rows the real count alerts on       2
rows .Count alerts on               0

No threshold above 100 can ever be crossed by the object count, so a check
written against .Count stays silent at every size that matters.
Common mistake: Two of those seven rows are a real emergency and the object count alerts on neither. A check written against .Count stays silent at every size that matters, and it looks correct in review because it works perfectly for a backlog of seven.

The helper calls its number Backlog and not Count for a related reason, and it is worth seeing before you rename it back. PowerShell gives every array and every scalar a Count of its own, so a property with that name is read correctly for as long as the pipeline returns exactly one bare object and silently stops being read the moment it does not.

expression            .Count    .Backlog      what .Count returned
----------            ------    --------      --------------------
$a = OneRow           2400      2400          the property
(OneRow)              2400      2400          the property
@(OneRow)             1         2400          the array length
$b = TwoRows          2         17,2400       the array length

threshold on .Count with two rows       False
threshold on the real maximum           True

One row answers with the property and looks correct. Two rows, or one @() around
the call, answer with the row count, and a threshold check reads 2 rather than
2400 and reports nothing wrong.
Warning: The first two rows are the trap: one object answers with the property, so the name survives every test run against a single replicated folder. Wrap the call in @(), or add a second folder, and the same expression starts returning the number of rows.

Example 3: tell a draining backlog from a stuck one

The problem: the backlog from DC01 to DC03 reads 61192 and nobody can say whether that is a server catching up after maintenance or a server that stopped.

The solution: Get-DfsrState reports what is in flight right now, and two backlog readings ten minutes apart report the direction of travel. Neither answer is available from a single backlog number.

# Get-DfsrState returns both inbound and outbound updates, so the Inbound column is
# what tells you which direction each update belongs to.
Get-DfsrState -ComputerName DC03 |
    Format-Table FileName, UpdateState, Inbound, SourceComputerName -AutoSize

# The only question a single reading cannot answer: is the number going down.
$first = (Get-BacklogRow -From DC01 -To DC03).Backlog
Start-Sleep -Seconds 600
$second = (Get-BacklogRow -From DC01 -To DC03).Backlog
'{0,-12}{1}' -f 'first', $first
'{0,-12}{1}' -f 'second', $second
'{0,-12}{1}' -f 'draining', ($second -lt $first)

The Get-DfsrState reference page describes the output as files currently replicating and files immediately queued to replicate next, and its own example shows UpdateState holding Scheduled for queued files and Downloading for files in flight. A large backlog with entries in Downloading is draining. A large backlog with nothing in flight is not.

Note: That page names its output type and lists none of its properties, so UpdateState is a column seen in an example rather than a documented member. Confirm the shape on your own machine with Get-DfsrState | Select-Object -First 1 | Format-List * before a script depends on it.

Example 4: make the service re-read its configuration

The problem: something changed in Active Directory, the change has replicated between domain controllers, and DFSR is still behaving as though it had not.

The solution: force the full poll instead of waiting for it. The Update-DfsrConfigurationFromAD reference page gives both intervals: “By default, the DFS Replication service queries domain controllers every five minutes by using a lightweight check for membership configuration changes in AD DS” and “By default, the DFS Replication service performs a full poll every hour.” The five minute interval is the lightweight check and the hourly one is the full poll, and only the second reloads the whole configuration.

# Without -Verbose this cmdlet prints nothing at all, which reads as a failure.
# Its documented output type is None, so the verbose stream is the only confirmation.
Update-DfsrConfigurationFromAD -ComputerName DC01, DC03 -Verbose

The page is explicit about the silence: “To return a response, use the Verbose parameter.” The CMD equivalent is dfsrdiag pollad, which is the exact line Microsoft’s sysvol procedure calls for at each of its steps.

Warning: A full poll makes the service re-read its configuration. It does not make a single file replicate, and it does not shorten a backlog. Boundary 5 covers the command that does.

Example 5: every direction, diffed against a baseline

The problem: three domain controllers means six directions to check, and the pair you suspect is one of the six.

The solution: sweep every ordered pair into one table, keep a baseline from a quiet moment, and compare the buckets rather than the counts. This is the last example because it is the one worth keeping.

# Every ordered pair, skipping the self-pair: a member has no backlog against itself
# and asking for one wastes a query per member on every run.
$dcs = 'DC01', 'DC02', 'DC03'

$report = foreach ($a in $dcs) {
    foreach ($b in $dcs) {
        if ($a -eq $b) { continue }
        Get-BacklogRow -From $a -To $b
    }
}

$report | Format-Table From, To, Folder, Backlog, State -AutoSize

Take the baseline once, when you believe replication is healthy, and keep the file. Comparing on State rather than on Backlog is what stops the diff reporting every row on every run, because a busy pair rarely holds the same count twice.

New-Item -ItemType Directory -Path C:\bat -Force | Out-Null

# Take the baseline once, at a quiet time, and keep it under version control if you can.
$report | Export-Clixml C:\bat\dfsr-baseline.xml

# Later, on the same shape of object. Compare the bucket, never the raw count.
$baseline = Import-Clixml C:\bat\dfsr-baseline.xml
Compare-Object $baseline $report -Property From, To, Folder, State

The run below is the code above with a lab fixture in place of the service: six ordered pairs over three domain controllers, one of them deliberately reporting no record at all, against a baseline taken when everything was quiet.

from   to     folder         backlog  state
----   --     ------         -------  -----
DC01   DC02   SYSVOL Share         0  clear
DC01   DC03   SYSVOL Share     61192  backlog
DC02   DC01   SYSVOL Share        11  latency
DC02   DC03   SYSVOL Share         2  latency
DC03   DC01   SYSVOL Share         0  clear
DC03   DC02   -                   -1  no record

ordered pairs queried           6
baseline properties             From,To,Folder,Backlog,State
report properties               From,To,Folder,Backlog,State
the two shapes match            True

from   to     folder         state      side
----   --     ------         -----      ----
DC01   DC03   SYSVOL Share   backlog    =>
DC02   DC03   SYSVOL Share   latency    =>
DC03   DC02   -              no record  =>
DC01   DC03   SYSVOL Share   clear      <=
DC02   DC03   SYSVOL Share   clear      <=
DC03   DC02   SYSVOL Share   clear      <=

pairs whose state moved         3
An empty table above is the pass, which reads like a failure the first time.
Result: An empty comparison table is the pass, and it reads like a failure the first time you see it. The run above is not a pass: three pairs moved. Two of them moved because a folder fell behind, and the third moved because DFSR stopped reporting that direction at all, which a report built on counts alone would have printed as a zero.

The same two servers under four different spellings

Three of the four rows below name the same two machines in the same order. The fourth looks as though it does and does not, which is the one that costs an afternoon.

CommandThe first nameThe second nameWhat the pair means
dfsrdiag backlog/sendingmember:/receivingmember:direction of replication
Get-DfsrBacklog-SourceComputerName-DestinationComputerNamedirection of replication
the same cmdlet, by alias-SMem-RMemdirection of replication
Write-DfsrHealthReport-ReferenceComputerName-MemberComputerNamea baseline server and everyone else, not a direction
Warning: The last row is the trap. -ReferenceComputerName looks like it belongs in the same family as -SourceComputerName and it does not: the health report takes one server as the baseline and compares the rest against it. Nothing in that pair says which way data flows.

The Get-DfsrBacklog reference page also documents both short aliases, -SMem for the sending member and -RMem for the receiving one, which is worth knowing when the line is already long enough to need a continuation.


The same check from a CMD prompt

dfsrdiag is a CMD tool and it prints its own count, which makes it the shorter route when a human is reading the answer. The archived Microsoft post on slow DFSR replication publishes a capture and describes the limit in the same breath: it shows “up to the first 100 file names, and also gives an accurate snapshot count”. The count is accurate above 100; the list is not complete above 100.

The shape is a count line followed by a numbered list. The block below carries this article’s server name and plausible sysvol file names in place of the ones in Microsoft’s capture, which is otherwise identical in form:

Member <DC03> Backlog File Count: 12
Backlog File Names (first 12 files)
1. File name: Registry.pol
2. File name: GptTmpl.inf
...

Pulling the number out for a scheduled task is one for /f loop. The count sits in the sixth whitespace-separated token of that first line.

rem Typed at a CMD prompt: ONE percent sign on the loop variable.
rem tokens=6 picks the number out of: Member <DC03> Backlog File Count: 12
for /f "tokens=6" %C in ('dfsrdiag backlog /rgname:"Domain System Volume" /rfname:"SYSVOL Share" /sendingmember:DC01 /receivingmember:DC03 ^| findstr /c:"Backlog File Count"') do @echo %C

The percent sign is the only part of that line that changes when it moves into a file. Microsoft’s for reference states the rule in two sentences: “Use a single percent sign (%) to carry out the for command at the command prompt. Use double percent signs (%%) to carry out the for command within a batch file.”

Where you are typing itLoop variableThe same line, shortened
A CMD prompt%Cfor /f "tokens=6" %C in ('...') do @echo %C
A .bat or .cmd file%%Cfor /f "tokens=6" %%C in ('...') do @echo %%C

The tokenizing is worth checking rather than trusting, and so is what happens when the query covers more than one replicated folder. The run below reimplements the default for /f split and applies it to a one folder capture and a two folder capture.

token   value
-----   -----
1       Member
2       <DC03>
3       Backlog
4       File
5       Count:
6       12

one folder    echoed: 12            a SET keeps only 12
two folders   echoed: 12 61192      a SET keeps only 61192

findstr matches one line per replicated folder, so the loop body runs once
per folder. A script that assigns the token to a variable keeps the last
one it saw, which is whichever folder dfsrdiag reported last.
Common mistake: With more than one replicated folder in scope, findstr matches one line per folder and the loop body runs once per folder. A script that assigns the token to a variable keeps whichever count DFSR reported last. Name the folder in the query and the problem does not arise.

The CMD side of Example 4 is one word long, and it is the line that appears at four separate steps across Microsoft’s two sysvol synchronisation procedures.

rem Typed at an elevated CMD prompt. Forces a full LDAP poll on the local server.
dfsrdiag pollad

Boundary 1: dfsdiag is not dfsrdiag

One letter separates two tools that serve two different subsystems, and only one of them has a reference page. dfsdiag diagnoses DFS Namespaces: its page opens with “Provides diagnostic information for DFS Namespaces.” and documents five subcommands, each introduced by a forward slash. dfsrdiag diagnoses DFS Replication, takes its subcommand as a bare word, and is not on Microsoft Learn at all.

ToolSubsystemSubcommand formReference page on Microsoft Learn
dfsdiagDFS Namespacesdfsdiag /testdcsyes, with five subcommands
dfsrdiagDFS Replicationdfsrdiag backlogno
dfsrmigsysvol migration from FRS to DFSRdfsrmig /getmigrationstateyes

Two checks agree on the missing page. The Windows Commands A to Z index lists dfsdiag with its five subcommands and lists dfsrmig, and contains no entry for dfsrdiag; and the URL that would follow the pattern of every other entry returns a 404.

Note: A third name in the family, dfsradmin, has no reference page either, and Microsoft’s sysvol procedure rules it out for this job in particular: “You can’t use the DFS Management snap-in (Dfsmgmt.msc) or the Dfsradmin.exe command-line tool to achieve this.” The namespace half of the family is covered separately in the dfsutil and dfsdiag guide.

Boundary 2: the group every domain controller has is hidden by default

Get-DfsReplicationGroup does not return the sysvol replication group unless you ask for it. The -IncludeSysvol parameter says why: “By default, this resource group is not returned as its management is controlled by Active Directory.” So an administrator who runs the cmdlet on a domain controller, sees an empty result and concludes DFSR is not replicating anything has read the output correctly and drawn the wrong conclusion.

Of the thirteen DFSR reference pages checked for this article, -IncludeSysvol appears on exactly one of them. Nothing on the Get-DfsrBacklog page says whether its wildcard covers sysvol, so naming the group is the only spelling that is not in doubt.

Both names contain a space, which means both are always quoted. The failure when they are not is worth seeing, because the error message names a word out of the group name and sends people looking for a problem with something else entirely. The run below declares a stand-in with the four documented parameters at their documented positions and reports what each one received.

quoted, which is correct:
GroupName                 [Domain System Volume]
FolderName                [SYSVOL Share]
SourceComputerName        [DC01]
DestinationComputerName   [DC03]

unquoted, the same four names:
binding failed            ParameterBindingException
message                   A positional parameter cannot be found that accepts argument 'System'.
Common mistake: The error reads “A positional parameter cannot be found that accepts argument ‘System’.” There is nothing wrong with the System account, the System log or the system state. A pair of quotation marks is missing.

Boundary 3: a backlog has a direction

The Get-DfsrBacklog page defines the measurement in one sentence: “Any files or folders listed in the DFS Replication backlog have not yet replicated from the source computer to the destination computer.” That is one direction between two members. Neither tool defaults to both ways, and a clean answer one way says nothing at all about the other.

So the unit of work is the ordered pair, not the member and not the connection. The run below counts them, and counts the self-queries a nested loop makes when nobody guards against them.

  members   naive NxN   ordered    self     one way
  -------   ---------   -------    ----    --------
        2           4         2       2           1
        3           9         6       3           3
        4          16        12       4           6
        8          64        56       8          28
       12         144       132      12          66

The nested loop with no guard asks every member for its backlog against itself:
one wasted query per member, every run.
Stopping at one direction per pair halves the queries and hides half the paths,
because a member that is behind inbound can be clean outbound.
Warning: A domain controller coming back from a restore is exactly the shape this catches: enormous inbound, nothing outbound. Check one direction per pair and it is even money whether you look at the half that is broken.

Boundary 4: the documented parse returns a string, and only one folder

The Get-DfsrBacklog page gives a one-liner for turning the verbose count into something a script can use. It splits the message on colons and takes the third field. The field it picks is the right one. What comes back is the wrong type, and depending on the shape of the output it is also the wrong folder or no answer at all.

First, the type. The split leaves the leading space in place, so the result is a string and every comparison against a threshold becomes a string comparison. The run below asks a backlog of 2400 two questions and gets both of them wrong, in opposite directions.

documented parse returns    [ 2400]
its type is                 String
its length is               5
equals the number 2400      False

a monitoring script asks two questions about a backlog of 2400:
  is it over 100            False  should be True
  is it under 500           True   should be False

cast to int gives           2400
  is it over 100            True   should be True
  is it under 500           False  should be False

Second, the scope. Leave -FolderName out and the cmdlet reports every replicated folder it finds, so the verbose stream carries one record per folder. Calling a method across that array runs it on every element and returns one flat list, so the index that meant the third field of one message still lands inside the first message and quietly discards the rest. Add the file records that 4>&1 merges into the same array and the expression stops working altogether.

records  parts  Split[2]   first folder in the stream
-------  -----  --------   --------------------------
1        3      [ 2400]    RF01
2        6      [ 2400]    RF01
3        9      [ 17]      SYSVOL Share

Split runs on every message and the results arrive as one flat list, so [2]
always lands inside the FIRST message. Row 3 reports 17 and never mentions
the folder that is 2400 behind.

array length with 4>&1        4
how many carry a Message      1
Split[2] on that array        You cannot call a method on a null-valued expression.

records  what the anchored parse returns
-------  -------------------------------
1        RF01=2400
2        RF01=2400, SYSVOL Share=17
3        SYSVOL Share=17, RF01=2400, RF03=9

Both faults have the same two-line fix, and both lines are in the helper from the prerequisites: filter to VerboseRecord before touching .Message, and anchor the pattern to the end of the line so the folder name and the count stay together.

Common mistake: The dangerous case is the third row. The one-liner returns a plausible number, sourced from whichever folder DFSR mentioned first, and there that is 17 while the folder sitting 2400 updates behind is never mentioned at all. A wrong number that looks right is worse than an error, because somebody writes it down.

Boundary 5: a full poll does not move a single file

dfsrdiag pollad and dfsrdiag syncnow are the two commands that get confused with each other when replication looks stuck, and they do different jobs. The poll makes the service re-read its configuration from Active Directory. The sync overrides the schedule so that data can actually move.

The Sync-DfsReplicationGroup page states the trade: “Use this cmdlet to alter the schedule temporarily to allow replication, because this cmdlet does not require Active Directory replication and LDAP polling.” When the schedule is closed that is the shorter path, because it needs neither the AD replication nor the LDAP poll that the other route waits on.

# Ignore the replication schedule from DC01 to DC03 for fifteen minutes. This is the
# cmdlet form of dfsrdiag syncnow and it needs no AD replication and no LDAP poll.
Sync-DfsReplicationGroup -GroupName 'Domain System Volume' `
    -SourceComputerName DC01 -DestinationComputerName DC03 -DurationInMinutes 15

The one place the poll is the operative step is sysvol recovery, and there it is doing exactly its documented job: making the service notice an attribute somebody changed in Active Directory. FRS had D2 and D4 values on a Bur Flags registry value for this; the sysvol procedure page opens by saying those values “don’t exist for the Distributed File System Replication (DFSR) service”, and that “sysvol replication is intentionally protected from any editing through its management interfaces to prevent accidents”. Confirmation is entirely event driven.

Event ID in the DFSR logWhat the procedure says it meansWhen it appears
4114sysvol replication is no longer being replicatedafter msDFSR-Enabled is set to FALSE
4614 and 4604sysvol replication has been initializedafter the non-authoritative poll, the D2 equivalent
4602sysvol replication has been initializedafter the authoritative poll, the D4 equivalent

Reading those four IDs out of the log is a Get-WinEvent filter rather than a scroll through Event Viewer, and the log to open is the one named DFS Replication. There is a separate guide on filtering event logs with Get-WinEvent.

# The DFSR log is small, so an ID filter is enough. 4114 means a folder went offline,
# 4602, 4604 and 4614 are the initialised messages the procedure tells you to expect.
Get-WinEvent -FilterHashtable @{ LogName = 'DFS Replication'; Id = 4114, 4602, 4604, 4614 } |
    Select-Object TimeCreated, Id, Message -First 10
Common mistake: The authoritative path is not a per-server fix. The procedure page is blunt about it: “If setting the authoritative flag on one DC, you must non-authoritatively synchronize all other DCs in the domain. Otherwise you’ll see conflicts on DCs, originating from any DCs where you did not set auth/non-auth and restarted the DFSR service.” Forcing the AD change out to every domain controller first is the step that makes the rest work, and repadmin is how you confirm it arrived.

What the reference pages do not say

The documentation for this corner of Windows Server has an age to it, and knowing where the gaps are saves an afternoon of assuming you misread something.

  • There is no dfsrdiag page. It is missing from the Windows Commands A to Z index, which does list dfsdiag and dfsrmig, and the URL that would match every other entry returns a 404. The subcommand names come off the tool’s own help text, which is why this article only names the ones a Microsoft page uses in an example.
  • Every one of the thirteen DFSR reference pages checked for this article carries ms.date: 12/20/2016. Eight of them are linked below; the other five are Get-DfsReplicatedFolder, Get-DfsrPreservedFiles, Get-DfsrConnection, Get-DfsrMember and Suspend-DfsReplicationGroup. The pages are published under the 2019, 2022 and 2025 monikers, and for Get-DfsrBacklog, Update-DfsrConfigurationFromAD and Get-DfsrState the 2019 and 2025 copies are identical apart from the version inside the online help link.
  • The description on the Get-DfsrBacklog page misspells its own cmdlet as Get-DfrsBacklog, in all three monikers.
  • The output type is named, Microsoft.DistributedFileSystemReplication.DfsrIdRecord, and not one of its properties is listed. The verbose message this whole article parses is documented only by appearing inside an example, which is why the parse here is anchored rather than positional.
  • A handful of parameters are marked as not accepting wildcards on the 2019 pages and as accepting them on the 2025 pages, with both copies stamped the same day. Treat that as a docs regeneration rather than a product change, and confirm on the machine you are standing at.

When the properties matter, the machine is the reference. One object and Format-List * settles it.

# The reference page names the output type and lists none of its properties.
Get-DfsrBacklog -GroupName 'Domain System Volume' -FolderName 'SYSVOL Share' `
        -SourceComputerName DC01 -DestinationComputerName DC03 |
    Select-Object -First 1 | Format-List *

Hidden gems

  • The health report does not count files unless you ask. -CountFiles says so in the parameter description: “By default, the cmdlet does not count files, in order to save time while generating a report.” A report generated without it tells you about state and nothing about volume.
  • -GroupName and -FolderName take wildcards, and that is the case that breaks the documented parse. -FolderName * reports every folder in one call, which is convenient right up to the moment something reads only the first record. The helper handles it; the one-liner does not.
  • $Matches survives a failed match. A pattern that does not match leaves the previous match’s captures in place, so a loop over several messages can read the previous line’s captures for a line that never matched. Read $Matches inside the if that tested the match, never after it.
  • Get-DfsrFileHash is DFSR’s own hash, not a general purpose one. The page says the value is “identical to the one computed by the Distributed File System (DFS) Replication service” and names no algorithm, so compare it only against another Get-DfsrFileHash result. A checksum from any other tool will not agree with it.
  • -SMem and -RMem shorten a line that already needs a continuation. Both are documented aliases, and on a four parameter call with two quoted names they are the difference between one line and two.
  • Never call an object property Count. Example 2 measures it: a single bare object answers with your property, and an array or an @() wrapper answers with the number of rows. The bug appears the day a second replicated folder joins the group.

The third gem is the one that quietly corrupts a report, so it is worth a run of its own. Three messages, one of which matches nothing, read once inside the if and once after it.

line  matched   read inside if  read after the if
----  -------   --------------  -----------------
1     True      SYSVOL Share=17 SYSVOL Share=17
2     False     not read        SYSVOL Share=17
3     True      RF01=2400       RF01=2400

Line 2 matched nothing and the column on the right still reports line 1, because
a failed match leaves the previous captures in place instead of clearing them.

Where this matters

  • A Group Policy change that reached some clients and not others. The policy is correct, gpresult shows it applied at one site and missing at another, and the backlog from the domain controller that made the edit is the number that explains it.
  • A domain controller back from a restore. Its inbound backlog is enormous and its outbound backlog is empty, which is why one direction per pair is not enough.
  • A monitoring check that has never fired. Written against the object count, it cannot cross any threshold above 100, and it passes review because it behaves correctly on a small backlog.
  • A logon script that exists on one domain controller only. sysvol is a replicated folder like any other, and the backlog is reported the same way, once the group name is spelled in full.
  • A closed replication schedule during a maintenance window. A full poll changes nothing; overriding the schedule for a fixed number of minutes is the command that does.
  • A sysvol recovery that went half done. Setting one domain controller authoritative without making the others non-authoritative produces conflicts, and the four DFSR event IDs are what tell you which stage each server reached.

Tips and limitations

  • Run both from an elevated prompt. Microsoft’s sysvol procedure calls for one, and the DFSR service has to be running on both members either way.
  • The object output of Get-DfsrBacklog stops at 100 files. The count in its verbose stream does not, and dfsrdiag backlog prints an accurate count above 100 as well.
  • Quote Domain System Volume and SYSVOL Share everywhere. Both contain a space and the binding error names a word from inside the group name.
  • A backlog number on its own is not a verdict. Two readings, or one reading plus Get-DfsrState, are.
  • Compare a sweep against a baseline on a bucket, not on a raw count, or every row differs on every run and the report stops being read.
  • sysvol cannot be edited through the DFS Management snap-in or dfsradmin, by design. The only documented route is the attribute and poll procedure, and it is worth reading in full before starting.
  • Everything here is read-only except three things: Sync-DfsReplicationGroup alters the schedule for a fixed number of minutes, Update-DfsrConfigurationFromAD forces a poll, and Example 5 writes its baseline to C:\bat.

Official documentation


  • Event Log Analyzer: the sysvol procedure is confirmed entirely through DFSR event IDs 4114, 4602, 4604 and 4614, and this is where you paste them.

Five cheat sheets, one PDF

Subnet masks, PowerShell, Linux commands, HTTP status codes and the ESXi command line - one page each, free to keep. Leave an address and it arrives in a minute.