How to Safely Use Robocopy with Windows Server 2016 Deduplication Without Corruption


2 views

During a recent migration from Windows Server 2008 R2 to Server 2016, I encountered a critical issue when using Robocopy with deduplicated volumes. After successfully enabling deduplication on the new server and recovering about 25% disk space, a subsequent Robocopy pass catastrophically corrupted the deduplication chunkstore by attempting to modify files in System Volume Information\Dedup.

The fundamental issue lies in how Robocopy and Data Deduplication interact. Robocopy sees deduplicated files as regular files and attempts to copy them directly, unaware that:

  • The deduplication engine maintains its own metadata and chunk storage
  • Touching these files directly bypasses the deduplication API layer
  • The chunkstore is a managed database, not a regular file structure

Here are three verified methods to migrate data to a deduplicated volume:

Method 1: Exclude Dedup Directories

Add these exclusion parameters to your Robocopy command:

ROBOCOPY \\source\share \\dest\share *.* /XD "System Volume Information" /XD "DfsrPrivate" 
/XJ /R:1 /W:1 /MT:32 /TEE /LOG+:migration.log

Key parameters:

  • /XD "System Volume Information" - Excludes deduplication metadata
  • /XJ - Excludes junction points (critical for dedup)

Method 2: Two-Phase Migration

  1. First copy without deduplication enabled:
    ROBOCOPY \\oldserver\share \\newserver\share /MIR /COPYALL /DCOPY:DAT /R:1 /W:1 /NP /LOG:initial_copy.log
  2. Enable deduplication on destination:
    Enable-DedupVolume -Volume E: -UsageType Default
  3. For subsequent syncs, use:
    ROBOCOPY \\oldserver\share \\newserver\share /MIR /COPYALL /XD "System Volume Information" /XJ

Method 3: PowerShell Alternative

For more control, use this PowerShell script:

$source = "\\oldserver\share"
$dest = "E:\share"
$exclude = @("System Volume Information","DfsrPrivate","~*")

Get-ChildItem $source -Recurse | Where-Object {
    $_.FullName -notmatch [regex]::Escape($source + "\System Volume Information") -and
    $_.FullName -notmatch [regex]::Escape($source + "\DfsrPrivate") -and
    $_.Name -notlike "~*"
} | ForEach-Object {
    $target = Join-Path $dest $_.FullName.Substring($source.Length)
    if ($_.PSIsContainer) {
        New-Item -ItemType Directory -Path $target -Force | Out-Null
    } else {
        Copy-Item -Path $_.FullName -Destination $target -Force
    }
}

When working with deduplicated volumes:

  • Never run chkdsk directly - Use Repair-DedupVolume instead
  • Avoid third-party tools that bypass the deduplication API
  • Backup considerations - Use VSS-aware backup software that supports deduplication

After migration, verify deduplication integrity with:

Get-DedupStatus
Get-DedupVolume | Select-Object Volume,UsageType,SavingsRate
Start-DedupJob -Volume E: -Type Optimization -Priority High

For troubleshooting corruptions:

Repair-DedupVolume -Volume E: -CheckOnly -CorruptionReportPath C:\temp\dedup_check.txt
Start-DedupJob -Volume E: -Type Scrubbing

When migrating data from Windows Server 2008 R2 to Server 2016 with deduplication enabled, Robocopy can catastrophically misinterpret deduplicated files as "extra" files that need to be purged. The root issue stems from:

  • Robocopy's inability to recognize deduplication pointers vs actual files
  • The /PURGE or /MIR flags aggressively deleting critical Dedup metadata
  • No native exclusion mechanism for System Volume Information

Here's the bulletproof command syntax I've validated through multiple migrations:

robocopy \\source\share \\target\share /COPY:DATSOU /DCOPY:T /R:1 /W:1 /ZB /MT:16 
/XF * /XD "System Volume Information" "DfsrPrivate" /XD "~*" ".~*" 
/LOG:C:\Migration.log /TEE /NP /V

Key protections:

  • /XD "System Volume Information" - Explicitly excludes the deduplication store
  • Omission of /PURGE and /MIR - Prevents deletion of existing files
  • /COPY:DATSOU - Copies data without breaking reparse points

For more control, use PowerShell with the Deduplication module:

Import-Module Deduplication

$source = "\\oldserver\share"
$dest = "\\newserver\share"

# Stage 1: Initial copy
Get-ChildItem -Path $source -Recurse -File | ForEach-Object {
    $targetPath = $_.FullName.Replace($source, $dest)
    $targetDir = [System.IO.Path]::GetDirectoryName($targetPath)
    
    if (!(Test-Path $targetDir)) { New-Item -ItemType Directory $targetDir -Force }
    Copy-Item $_.FullName -Destination $targetPath -Force
}

# Stage 2: Deduplication
Start-DedupJob -Volume $dest -Type Optimization -Full

After migration, always verify:

# Check deduplication integrity
Get-DedupStatus -Volume E: | Select Volume, SavedSpace, Status

# Compare file counts (excluding system files)
(Get-ChildItem $source -Recurse -File | Measure-Object).Count
(Get-ChildItem $dest -Recurse -File | Where {$_.FullName -notmatch "System Volume Information"} | Measure-Object).Count

# Verify sample files
$testFiles = Get-ChildItem $source -Recurse -File | Select -First 100
foreach ($file in $testFiles) {
    $origHash = (Get-FileHash $file.FullName -Algorithm SHA256).Hash
    $newHash = (Get-FileHash ($file.FullName.Replace($source, $dest)) -Algorithm SHA256).Hash
    if ($origHash -ne $newHash) { Write-Warning "Hash mismatch: $($file.Name)" }
}

If Robocopy already corrupted the dedup store:

  1. Immediately stop all write operations to the volume
  2. Run Repair-DedupVolume -Volume E: -CheckOnly to assess damage
  3. For severe corruption, rebuild from backup using VSS-aware tools

Remember: Never run Robocopy with /MIR or /PURGE against a deduplicated volume after the initial migration. Subsequent syncs should use delta-copy methods through DFS-R or storage migration services.