PowerShell Backup Script: Automated File Backup with Robocopy

A production server backup that runs nightly, emails you pass/fail, logs every file operation, and requires zero manual intervention is the gold standard for operational reliability. This PowerShell backup script built around Robocopy achieves exactly that: incremental file copies, a structured log, an exit-code check, and an email summary — all wired together in a script that runs as a scheduled task without supervision. This post builds the complete solution step by step.
Robocopy Basics in PowerShell
Robocopy is a built-in Windows command for robust file copying. Calling it from PowerShell is straightforward — it runs as an external command and returns an exit code you can inspect:
$source = "C:\AppData"
$destination = "\\nas01\backup\AppData"
# Basic copy preserving attributes and timestamps
robocopy $source $destination /E /COPYALL /R:3 /W:5
# Check exit code
Write-Host "Robocopy exit code: $LASTEXITCODE"
Robocopy exit code: 1
Robocopy exit codes are cumulative bit flags: 0 = no files copied (already in sync), 1 = files copied successfully, 2 = extra files in destination, 4 = mismatched files, 8 = copy failures. Exit codes 0 through 7 indicate various forms of success or partial success; 8 or higher indicate failures. This is a critical distinction — exit code 1 is success, not an error.
Incremental Backup with /MIR
The /MIR switch mirrors the source to the destination: copying new and changed files, and deleting destination files that no longer exist in the source. This creates an exact, space-efficient copy:
$source = "C:\WebApp\Content"
$destination = "\\nas01\backup\WebContent"
$logFile = "C:\Logs\backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
robocopy $source $destination /MIR /COPYALL /R:3 /W:10 /NP /LOG:$logFile
$exitCode = $LASTEXITCODE
Write-Host "Exit code: $exitCode | Log: $logFile"
Caution: /MIR deletes files in the destination that are not in the source. Never use it against a backup that stores multiple versions — use a versioned backup tool for that scenario.
Log Robocopy Output
The /LOG: switch writes Robocopy’s output to a file instead of the console. Use /TEE to write to both the log file and the console simultaneously:
$logDir = "C:\Logs\Backup"
$logFile = Join-Path $logDir "backup_$(Get-Date -Format 'yyyyMMdd').log"
if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory | Out-Null }
robocopy "C:\AppData" "\\nas01\backup\AppData" /MIR /R:3 /W:5 /NP /LOG+:$logFile /TEE
Using /LOG+: (with the plus) appends to the log file rather than overwriting, allowing multiple backup runs to accumulate in one daily log.
Check Exit Code for Success/Failure
Map Robocopy’s numeric exit code to a meaningful status string. Exit codes 0–7 are success variants; 8 and above indicate actual failures:
function Get-RobocopyStatus {
param([int]$ExitCode)
switch ($ExitCode) {
0 { "Success — no changes (destination already current)" }
1 { "Success — files copied" }
2 { "Success — extra files in destination" }
3 { "Success — files copied and extra files in destination" }
{ $_ -ge 8 } { "FAILURE — one or more files could not be copied" }
default { "Partial success — code $ExitCode" }
}
}
robocopy "C:\AppData" "\\nas01\backup\AppData" /MIR /R:3 /W:5 /NP /LOG+:$logFile
$status = Get-RobocopyStatus -ExitCode $LASTEXITCODE
Write-Host "Backup status: $status"
$failed = $LASTEXITCODE -ge 8
Email Summary Report
Send a summary email after the backup completes, including the last few lines of the log file for quick review:
$logTail = Get-Content $logFile -Tail 20 | Out-String
$subject = if ($failed) {
"BACKUP FAILED: AppData on $env:COMPUTERNAME"
} else {
"Backup OK: AppData — $status"
}
$body = @"
Backup completed at $(Get-Date)
Status: $status (exit code $LASTEXITCODE)
Source: C:\AppData
Destination: \\nas01\backup\AppData
Log: $logFile
--- Last 20 lines of log ---
$logTail
"@
Send-MailMessage -From "[email protected]" -To "[email protected]" `
-Subject $subject -Body $body -SmtpServer "smtp-relay.corp.com"
Schedule with Task Scheduler
Register the backup script as a nightly scheduled task running at 1 AM:
$action = New-ScheduledTaskAction -Execute "pwsh.exe" `
-Argument '-NonInteractive -File "C:\Scripts\Backup-AppData.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At "01:00"
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 3)
Register-ScheduledTask -TaskName "NightlyBackup-AppData" -Action $action `
-Trigger $trigger -Settings $settings -RunLevel Highest `
-Description "Nightly incremental backup of AppData to NAS" -Force
Write-Host "Backup scheduled for 01:00 daily"
Common Errors and Fixes
-
Robocopy exit code 1 means success with copies — not an error. PowerShell scripts that check
if ($LASTEXITCODE -ne 0)after Robocopy will falsely report failure when files were copied. Always check-ge 8for actual failures, not-ne 0. -
/MIR deletes files in destination not in source — be careful. If someone manually placed files in the backup destination,
/MIRwill delete them on the next run. Use/Einstead of/MIRif you want a one-way copy that never deletes from the destination.
Related Cmdlets / See Also
Wrapping Up
A Robocopy-backed PowerShell backup script with proper exit code interpretation, structured logging, email alerts, and a Task Scheduler registration covers every aspect of production-grade backup automation. The critical details are correctly interpreting exit code 1 as success, using /LOG+: for accumulated logs, and understanding that /MIR is destructive to the destination.


