PowerShell Script to Monitor and Alert on Failed Backups
A failed backup is only a minor inconvenience until the day you actually need that backup and discover it hasn't run successfully in three weeks. Most backup software logs failures somewhere, but unless someone is actively checking that log every day, silent failures pile up unnoticed. This PowerShell script to monitor backup jobs checks your backup logs and job status across servers on a schedule and sends an immediate email alert the moment something fails, so you find out within minutes instead of during a disaster recovery scenario.
This tutorial builds a monitoring script you can adapt to Windows Server Backup, a third-party backup product's log format, or a simple file-based backup verification check.
Why Backup Failures Go Unnoticed for Weeks
Backup jobs typically run overnight or during off-hours specifically so they don't disrupt the workday, which also means nobody is watching when they fail. Most backup software sends a notification email on failure, but those emails get buried in inboxes, filtered into folders nobody checks, or simply ignored after the tenth "backup completed with warnings" email that turned out to be nothing.
The deeper problem is that a single missed notification email doesn't get escalated. If the backup job fails silently at the infrastructure level — the destination drive fills up, a scheduled task stops running, or a service account's credentials expire — there's often no active alerting at all, just a log entry that nobody is checking until it's too late.
Building the Backup Monitoring Script
Step 1: Query Windows Server Backup Status
If you're using Windows Server Backup, PowerShell's built-in Get-WBSummary and Get-WBJob cmdlets expose recent job status directly:
1Import-Module WindowsServerBackup23function Get-BackupStatus {4 $summary = Get-WBSummary5 return [PSCustomObject]@{6 LastBackupTime = $summary.LastBackupTime7 LastBackupResult = $summary.LastBackupResultHR8 NextBackupTime = $summary.NextBackupTime9 }10}
A non-zero LastBackupResultHR indicates the most recent backup job did not complete successfully.
Step 2: Check Backup Age as a Safety Net
Even if the result code looks successful, a backup job that silently stopped running entirely wouldn't show a failure — it just wouldn't show anything recent. Add an age check as a second layer of verification:
1function Test-BackupIsRecent {2 param(3 [datetime]$LastBackupTime,4 [int]$MaxAgeHours = 265 )6 $age = (Get-Date) - $LastBackupTime7 return $age.TotalHours -le $MaxAgeHours8}
Setting the threshold to 26 hours rather than exactly 24 gives a small buffer for backups that run slightly later than usual without triggering false alarms every day.
Step 3: Check Multiple Servers Remotely
For an environment with several servers, loop through them using PowerShell remoting rather than logging into each one manually:
1$servers = @("FS01", "APP02", "DB03")2$results = @()34foreach ($server in $servers) {5 try {6 $status = Invoke-Command -ComputerName $server -ScriptBlock {7 Import-Module WindowsServerBackup8 Get-WBSummary | Select-Object LastBackupTime, LastBackupResultHR9 } -ErrorAction Stop1011 $results += [PSCustomObject]@{12 Server = $server13 LastBackupTime = $status.LastBackupTime14 LastBackupResult = $status.LastBackupResultHR15 Reachable = $true16 }17 }18 catch {19 $results += [PSCustomObject]@{20 Server = $server21 LastBackupTime = $null22 LastBackupResult = "Unreachable"23 Reachable = $false24 }25 }26}
Wrapping each remote check in a try/catch matters here — a server that's unreachable due to a network issue or being powered off should be flagged as a monitoring gap in its own right, not silently skipped.
Step 4: Send Alert Emails for Any Failure or Gap
1function Send-BackupAlert {2 param($FailedResults)34 $body = "The following backup issues were detected:`n`n"5 foreach ($item in $FailedResults) {6 $body += "Server: $($item.Server) | Last Backup: $($item.LastBackupTime) | Result: $($item.LastBackupResult)`n"7 }89 Send-MailMessage -From "backup-monitor@company.com" `10 -To "itops@company.com" `11 -Subject "ALERT: Backup Failures Detected" `12 -Body $body `13 -SmtpServer "smtp.company.com" `14 -Priority High15}1617$failures = $results | Where-Object {18 $_.LastBackupResult -ne 0 -or -not (Test-BackupIsRecent -LastBackupTime $_.LastBackupTime)19}2021if ($failures.Count -gt 0) {22 Send-BackupAlert -FailedResults $failures23}
Scheduling the Monitoring Script
Register the script to run every morning using Task Scheduler so failures are caught early in the day rather than discovered by accident:
1$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `2 -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Check-BackupStatus.ps1"3$trigger = New-ScheduledTaskTrigger -Daily -At "7:00AM"4Register-ScheduledTask -TaskName "BackupStatusCheck" -Action $action -Trigger $trigger -RunLevel Highest
Running it at 7 AM, after overnight jobs finish but well before the business day is in full swing, gives IT staff time to investigate and re-run a failed job before it becomes a bigger problem.
Real-World Example: Catching a Silent Failure Before It Mattered
An IT team running backups across eight file and database servers discovered, after deploying this monitoring script, that one server's backup job had silently stopped running eleven days earlier following a Windows update that disabled the scheduled task without any error notification. The age-check safeguard flagged the gap on the very first monitoring run, well before that server experienced any actual data loss event that would have made the missing backups a genuine crisis instead of a quick fix.
Best Practices / Pro Tips
Always include the backup age check alongside the result code check. A stopped or disabled backup job frequently doesn't generate a failure code at all — it simply stops generating any new result.
Alert to a shared distribution list or ticketing system integration rather than a single person's inbox, so the alert doesn't get missed during someone's time off.
Log every check to a file or centralized logging system even when there's no failure, so you have a historical record to prove backups were verified daily if that's ever needed for a compliance audit.
Test the alerting path itself periodically by intentionally simulating a failure, since an alerting script that silently breaks is exactly as dangerous as the backup failures it's supposed to catch.
Conclusion
A properly built PowerShell script to monitor backup jobs closes the gap between "the backup software sent an email" and "someone actually saw and acted on it," combining result-code checking with an age-based safety net and proactive email alerts. Running this check every morning across all your servers turns backup verification from a manual, easily-skipped task into a reliable automated safeguard that catches failures within hours instead of weeks.
Frequently Asked Questions
What if we use a third-party backup product instead of Windows Server Backup?
Most backup products expose PowerShell modules or log files you can parse similarly — check your vendor's documentation for a PowerShell module, or adapt the script to parse the product's log file for success/failure keywords and timestamps instead of using Get-WBSummary.
How do I avoid alert fatigue from this script?
Only alert on genuine failures or backups older than your defined threshold, and consolidate multiple server failures into a single digest email rather than one email per server, so a widespread issue doesn't flood your team's inbox.
Can this script also verify backup file integrity, not just job status?
Yes, with an additional step — after confirming the job succeeded, add a check that the destination backup file exists and meets a minimum expected file size, which catches jobs that report success but produced a truncated or corrupt backup file.
How often should this script run?
Once daily is sufficient for most environments, but if your recovery point objective requires near-continuous protection, running the check every few hours provides faster detection of a failure at the cost of slightly more alert volume to manage.
Related articles: PowerShell Script to Audit Local Admin Accounts Network-Wide, Monitor System Health with a PowerShell Dashboard, Automate Data Backups with Python
Sponsored Content
Interested in advertising? Reach automation professionals through our platform.
