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.