PowerShell Backup Script: Automated File Backup with Robocopy

PowerShell Backup Script: Automated File Backup with Robocopy

PowerShell Tips Editor 2 min read
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 8 for actual failures, not -ne 0.
  • /MIR deletes files in destination not in source — be careful. If someone manually placed files in the backup destination, /MIR will delete them on the next run. Use /E instead of /MIR if 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.

Send-Item -To