How to Configure Scheduled Task-Based Backup Scripts on Windows Server 2012 R2
While Windows Server Backup provides a built-in GUI-based backup solution, many enterprise environments require custom backup scripts that handle specific scenarios not covered by WSB — such as application-specific export procedures, database dumps, configuration file backups, scripted verification of backup completeness, and integration with change management workflows. Windows Server 2012 R2’s Task Scheduler combined with PowerShell provides a powerful platform for building automated backup orchestration. This guide covers designing, implementing, scheduling, and monitoring a comprehensive custom backup script framework.
Prerequisites
PowerShell 4.0 (included with Windows Server 2012 R2). A service account with appropriate permissions to read the data being backed up, write to the backup destination, and send email via the specified SMTP server. A backup destination (local volume, network share, or both) with sufficient capacity. Administrator rights to create and manage Scheduled Tasks. A directory structure for scripts, logs, and temporary working files.
Step 1: Design the Backup Script Framework
Create a robust PowerShell backup framework with the following components. Create the directory structure:
New-Item -ItemType Directory -Path "C:BackupScripts" -Force | Out-Null
New-Item -ItemType Directory -Path "C:BackupScriptsLogs" -Force | Out-Null
New-Item -ItemType Directory -Path "C:BackupScriptsConfig" -Force | Out-Null
New-Item -ItemType Directory -Path "C:BackupScriptsModules" -Force | Out-Null
Step 2: Create the Core Backup Module
Create a reusable logging and notification module at C:BackupScriptsModulesBackupHelpers.psm1:
function Write-BackupLog {
param(
[string]$Message,
[string]$Level = "INFO",
[string]$LogFile
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
Add-Content -Path $LogFile -Value $logEntry
Write-Host $logEntry
}
function Send-BackupAlert {
param(
[string]$Subject,
[string]$Body,
[string]$SmtpServer,
[string]$From,
[string]$To
)
try {
Send-MailMessage -SmtpServer $SmtpServer -From $From -To $To -Subject $Subject -Body $Body -ErrorAction Stop
} catch {
Write-Host "Failed to send email alert: $_" -ForegroundColor Red
}
}
function Test-BackupDestination {
param([string]$Path)
if (-not (Test-Path $Path)) {
try {
New-Item -ItemType Directory -Path $Path -Force | Out-Null
return $true
} catch {
return $false
}
}
return $true
}
Export-ModuleMember -Function Write-BackupLog, Send-BackupAlert, Test-BackupDestination
Step 3: Create the Main Backup Orchestration Script
Create the main backup script at C:BackupScriptsRunBackup.ps1:
param(
[string]$ConfigFile = "C:BackupScriptsConfigbackup.config.ps1",
[switch]$TestMode
)
Import-Module "C:BackupScriptsModulesBackupHelpers.psm1" -Force
# Load configuration
. $ConfigFile
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$logFile = "C:BackupScriptsLogsBackup_$timestamp.log"
$exitCode = 0
$errors = @()
Write-BackupLog "=== Backup job started on $env:COMPUTERNAME ===" -Level INFO -LogFile $logFile
Write-BackupLog "Config: $ConfigFile" -Level INFO -LogFile $logFile
# Task 1: Verify backup destination is accessible
if (-not (Test-BackupDestination -Path $config.BackupDestination)) {
Write-BackupLog "ERROR: Cannot access backup destination: $($config.BackupDestination)" -Level ERROR -LogFile $logFile
$errors += "Backup destination not accessible"
$exitCode = 1
} else {
Write-BackupLog "Backup destination accessible: $($config.BackupDestination)" -Level INFO -LogFile $logFile
}
# Task 2: Backup IIS configuration
if ($config.BackupIIS -and $exitCode -eq 0) {
try {
$iisBackupName = "IIS_$timestamp"
Write-BackupLog "Backing up IIS configuration..." -Level INFO -LogFile $logFile
if (-not $TestMode) {
& "$env:windirsystem32inetsrvappcmd.exe" add backup $iisBackupName
}
Write-BackupLog "IIS backup completed: $iisBackupName" -Level INFO -LogFile $logFile
} catch {
Write-BackupLog "ERROR: IIS backup failed: $_" -Level ERROR -LogFile $logFile
$errors += "IIS backup failed"
}
}
# Task 3: Copy configuration files
if ($config.ConfigPaths -and $exitCode -eq 0) {
foreach ($configPath in $config.ConfigPaths) {
$destPath = Join-Path $config.BackupDestination "Config$(Split-Path $configPath -Leaf)"
try {
Write-BackupLog "Copying config: $configPath -> $destPath" -Level INFO -LogFile $logFile
if (-not $TestMode) {
robocopy $configPath (Join-Path $config.BackupDestination "Config") /E /R:2 /W:5 /LOG+:$logFile /NP | Out-Null
}
} catch {
Write-BackupLog "ERROR: Config copy failed for $configPath`: $_" -Level ERROR -LogFile $logFile
$errors += "Config copy failed: $configPath"
}
}
}
# Task 4: Verify backup completeness
$backupFiles = Get-ChildItem -Path $config.BackupDestination -Recurse -File -ErrorAction SilentlyContinue
$totalSizeMB = [math]::Round(($backupFiles | Measure-Object Length -Sum).Sum / 1MB, 2)
Write-BackupLog "Backup verification: $($backupFiles.Count) files, $totalSizeMB MB" -Level INFO -LogFile $logFile
# Task 5: Clean up old backups
$oldBackups = Get-ChildItem $config.BackupDestination -Directory | Where-Object {
$_.LastWriteTime -lt (Get-Date).AddDays(-$config.RetentionDays)
}
foreach ($old in $oldBackups) {
Write-BackupLog "Removing old backup: $($old.FullName)" -Level INFO -LogFile $logFile
if (-not $TestMode) { Remove-Item $old.FullName -Recurse -Force }
}
# Final status and notification
$endTime = Get-Date
$duration = [math]::Round(((Get-Date) - [datetime]$timestamp.Substring(0,8)).TotalMinutes, 1)
$status = if ($errors.Count -eq 0) { "SUCCESS" } else { "FAILED" }
Write-BackupLog "=== Backup $status. Duration: ${duration}min. Errors: $($errors.Count) ===" -Level INFO -LogFile $logFile
$emailBody = "Backup $status on $env:COMPUTERNAME`n"
$emailBody += "Date: $(Get-Date)`n"
$emailBody += "Duration: ${duration} minutes`n"
$emailBody += "Files backed up: $($backupFiles.Count) ($totalSizeMB MB)`n"
if ($errors.Count -gt 0) { $emailBody += "Errors:`n$($errors -join "`n")`n" }
Send-BackupAlert -Subject "Backup $status - $env:COMPUTERNAME" -Body $emailBody -SmtpServer $config.SmtpServer -From $config.EmailFrom -To $config.EmailTo
exit $exitCode
Step 4: Create the Configuration File
Create the configuration file at C:BackupScriptsConfigbackup.config.ps1:
$config = @{
BackupDestination = "\BackupServer01Backups$env:COMPUTERNAME"
BackupIIS = $true
ConfigPaths = @(
"C:inetpubwwwrootConfig",
"D:AppDataConfig"
)
RetentionDays = 30
SmtpServer = "smtp.yourdomain.com"
EmailFrom = "[email protected]"
EmailTo = "[email protected]"
}
Step 5: Schedule the Backup Task
Register the backup script as a scheduled task with a dedicated service account:
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NonInteractive -ExecutionPolicy Bypass -File C:BackupScriptsRunBackup.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "11:00PM"
$principal = New-ScheduledTaskPrincipal -UserId "DOMAINsvc-backup" -RunLevel Highest -LogonType Password
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 4) -MultipleInstances IgnoreNew -StartWhenAvailable
Register-ScheduledTask -TaskName "Nightly Server Backup" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Nightly automated backup script"
Step 6: Monitor Scheduled Task Execution
Monitor the scheduled task and backup log results:
# Check last run result of the backup task
Get-ScheduledTask -TaskName "Nightly Server Backup" | Get-ScheduledTaskInfo | Select-Object TaskName, LastRunTime, LastTaskResult, NextRunTime
# View latest backup log
$latestLog = Get-ChildItem "C:BackupScriptsLogs" -Filter "*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
Get-Content $latestLog.FullName
Summary
A well-structured scheduled task backup framework on Windows Server 2012 R2 provides flexibility and extensibility beyond built-in backup tools. The modular design with a shared helpers module, configuration file, and main orchestration script makes it straightforward to add new backup tasks, adjust configuration, and extend to new servers. Detailed logging, email notifications, and integration with Task Scheduler provide the operational visibility needed to ensure backup jobs run reliably every night and failures are caught and resolved before they become multi-day backup gaps.