PowerShell File Sync Script: Keep Two Folders in Sync

PowerShell File Sync Script: Keep Two Folders in Sync

PowerShell Tips Editor 3 min read
PowerShell File Sync Script: Keep Two Folders in Sync

Keeping your laptop’s working folder synchronized with a network share — or keeping two servers’ data directories in sync — requires more than a simple Copy-Item. You need delta detection, conflict handling, and a log of what changed. A PowerShell sync folders script built around Robocopy handles all of this reliably, with one-way and two-way sync modes, file exclusions, and scheduled execution. This post covers each approach with practical examples.

One-Way Sync with Robocopy /MIR

The /MIR (mirror) switch is the simplest one-way sync: it copies new and changed files from source to destination, and deletes destination files that no longer exist in the source. The destination becomes an exact mirror of the source:

$source      = "C:\Projects\WebApp"
$destination = "\\fileserver\backup\WebApp"
$logFile     = "C:\Logs\sync_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"

robocopy $source $destination /MIR /COPYALL /R:3 /W:5 /NP /LOG+:$logFile

$exitCode = $LASTEXITCODE
Write-Host "Sync complete. Exit code: $exitCode"
Write-Host "Log: $logFile"

if ($exitCode -ge 8) {
    Write-Warning "Robocopy reported failures — check $logFile"
}

Important: /MIR will delete files in $destination that are not in $source. This is the correct behavior for a mirror sync, but it means any manual files added to the destination will be removed on the next sync run.

Delta-Only Copies with /XO

The /XO (exclude older) switch copies only files that are newer in the source than the destination, without deleting anything from the destination. This is a safe incremental copy that never removes files:

$source      = "C:\Reports\Daily"
$destination = "\\nas01\archive\DailyReports"

# Copy new and updated files only — never delete from destination
robocopy $source $destination /E /XO /R:3 /W:5 /NP

# Count files copied
if ($LASTEXITCODE -in 0,1,2,3) {
    Write-Host "Incremental copy successful"
} else {
    Write-Warning "Copy failed or partial — exit code $LASTEXITCODE"
}

Exclude File Types and Folders

Robocopy supports exclusion of specific file extensions, files matching patterns, and entire folder names. This is essential for excluding temp files, version control metadata, and build artifacts:

$source      = "C:\DevProjects\AppSrc"
$destination = "\\nas\DevBackup\AppSrc"

robocopy $source $destination /MIR /R:3 /W:5 /NP /LOG+:"C:\Logs\dev-sync.log" `
    /XF "*.tmp" "*.log" "*.bak" "Thumbs.db" `   # exclude these file patterns
    /XD "node_modules" ".git" "bin" "obj"        # exclude these folder names

Two-Way Sync Strategy

True two-way sync (where changes on either side propagate to the other) is inherently complex due to conflict scenarios. Robocopy does not support two-way sync natively. A practical approach is to designate one side as the “master” and sync in two passes with conflict detection between them:

$sideA = "C:\LocalWork"
$sideB = "\\fileserver\SharedWork"

# Step 1: Sync A → B (newer files win)
robocopy $sideA $sideB /E /XO /R:2 /W:3 /NP /LOG+:"C:\Logs\sync-AtoB.log"

# Step 2: Sync B → A (pick up files changed on B)
robocopy $sideB $sideA /E /XO /R:2 /W:3 /NP /LOG+:"C:\Logs\sync-BtoA.log"

Write-Host "Two-way sync complete"

Conflict Resolution

When the same file is modified on both sides between sync runs, the last-write-time comparison in /XO determines which version wins. For critical files, log conflicts explicitly before syncing:

function Find-SyncConflicts {
    param([string]$PathA, [string]$PathB)

    $filesA = Get-ChildItem -Path $PathA -Recurse -File |
        Select-Object @{N='RelPath';E={$_.FullName.Substring($PathA.Length)}}, LastWriteTime
    $filesB = Get-ChildItem -Path $PathB -Recurse -File |
        Select-Object @{N='RelPath';E={$_.FullName.Substring($PathB.Length)}}, LastWriteTime

    $inBoth = $filesA.RelPath | Where-Object { $_ -in $filesB.RelPath }

    foreach ($rel in $inBoth) {
        $timeA = ($filesA | Where-Object RelPath -eq $rel).LastWriteTime
        $timeB = ($filesB | Where-Object RelPath -eq $rel).LastWriteTime
        $diff  = [Math]::Abs(($timeA - $timeB).TotalSeconds)
        if ($diff -gt 5) {
            Write-Warning "CONFLICT: $rel (A: $timeA | B: $timeB)"
        }
    }
}

Find-SyncConflicts -PathA "C:\LocalWork" -PathB "\\fileserver\SharedWork"

Log Changes and Schedule

Parse the Robocopy log to extract a summary of what changed, then schedule the sync as a recurring task:

$logFile = "C:\Logs\sync_latest.log"
robocopy "C:\Reports" "\\nas01\Reports" /MIR /R:3 /W:5 /LOG:$logFile

# Parse summary from log
$summary = Select-String -Path $logFile -Pattern "^\s+(Dirs|Files)\s*:" | Select-Object -Last 2
Write-Host "Sync summary:"
$summary | ForEach-Object { Write-Host "  $($_.Line.Trim())" }

# Schedule as daily task
$action  = New-ScheduledTaskAction -Execute "pwsh.exe" `
    -Argument '-NonInteractive -File "C:\Scripts\Sync-Reports.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At "23:00"
Register-ScheduledTask -TaskName "DailyReportSync" -Action $action -Trigger $trigger `
    -RunLevel Highest -Force

Common Errors and Fixes

  • /MIR deletes destination files not in source — intended but dangerous. Before running a mirror sync for the first time, do a preview run with /L (list only — no actual copies or deletes) to see what Robocopy would delete: robocopy $source $destination /MIR /L /NP. Review the output carefully before removing /L.
  • UNC paths with spaces need quoting. UNC paths containing spaces (e.g., \\server\My Share\Folder) must be quoted when passed to Robocopy: robocopy "$source" "$destination" /MIR. PowerShell double-quotes work correctly here.

Related Cmdlets / See Also

Wrapping Up

Robocopy provides reliable, Windows-native folder synchronization from PowerShell. Use /MIR for exact mirroring, /E /XO for safe incremental copies, and /XF /XD for exclusions. Always preview with /L before your first mirror sync, log every run with /LOG+:, and schedule with Task Scheduler for hands-free execution.

Send-Item -To