a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
643 lines
23 KiB
PowerShell
643 lines
23 KiB
PowerShell
###############################################################################
|
|
#### windows-backup-smoke-tests.ps1 — Verify Windows backup health ####
|
|
#### Checks WBAdmin status, backup age, VSS writers, shadow copies, ####
|
|
#### system state, event log errors, SQL backup age, BMR readiness. ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version: 1.0 ####
|
|
#### ####
|
|
#### Usage: .\windows-backup-smoke-tests.ps1 ####
|
|
#### .\windows-backup-smoke-tests.ps1 -BackupTarget E:\Backups ####
|
|
#### .\windows-backup-smoke-tests.ps1 -OutputFormat tap ####
|
|
#### ####
|
|
#### See -Help for all options. ####
|
|
###############################################################################
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$BackupTarget = "",
|
|
[int]$MaxBackupAgeHours = 25,
|
|
[ValidateSet("text","tap")]
|
|
[string]$OutputFormat = "text",
|
|
[switch]$NoColor,
|
|
[switch]$Help
|
|
)
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
|
|
# ============================================================================
|
|
# HELP
|
|
# ============================================================================
|
|
|
|
if ($Help) {
|
|
@"
|
|
Usage: .\windows-backup-smoke-tests.ps1 [OPTIONS]
|
|
|
|
Smoke-test Windows backup infrastructure. PowerShell 5.1+.
|
|
Designed for Windows Server with Windows Server Backup.
|
|
|
|
Parameters:
|
|
-BackupTarget PATH Backup destination path (default: auto-detect from WBAdmin)
|
|
-MaxBackupAgeHours HOURS Maximum backup age in hours (default: 25)
|
|
-OutputFormat FORMAT Output: text (default), tap
|
|
-NoColor Disable coloured output
|
|
-Verbose Show debug output
|
|
-Help Show this help
|
|
|
|
Examples:
|
|
.\windows-backup-smoke-tests.ps1
|
|
.\windows-backup-smoke-tests.ps1 -BackupTarget "E:\WindowsImageBackup"
|
|
.\windows-backup-smoke-tests.ps1 -MaxBackupAgeHours 48 -OutputFormat tap
|
|
.\windows-backup-smoke-tests.ps1 -NoColor -Verbose
|
|
"@
|
|
exit 0
|
|
}
|
|
|
|
# ============================================================================
|
|
# STATE
|
|
# ============================================================================
|
|
|
|
$script:Pass = 0
|
|
$script:Fail = 0
|
|
$script:Skip = 0
|
|
$script:Total = 0
|
|
$script:Results = @()
|
|
$script:StartTime = Get-Date
|
|
|
|
# ============================================================================
|
|
# COLORS
|
|
# ============================================================================
|
|
|
|
function Write-Color {
|
|
param([string]$Text, [string]$Color = "White")
|
|
if ($NoColor) {
|
|
Write-Host $Text
|
|
} else {
|
|
Write-Host $Text -ForegroundColor $Color
|
|
}
|
|
}
|
|
|
|
function Write-Log { param([string]$Msg) Write-Color "[INFO] $Msg" "Cyan" }
|
|
function Write-Warn { param([string]$Msg) Write-Color "[WARN] $Msg" "Yellow" }
|
|
function Write-Err { param([string]$Msg) Write-Color "[ERROR] $Msg" "Red" }
|
|
|
|
# ============================================================================
|
|
# TEST RESULT RECORDING
|
|
# ============================================================================
|
|
|
|
function Record-Pass {
|
|
param([string]$Name, [string]$Detail = "")
|
|
$script:Pass++
|
|
$script:Total++
|
|
$script:Results += [PSCustomObject]@{ Status="PASS"; Name=$Name; Detail=$Detail }
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "ok $($script:Total) - $Name$(if($Detail){" ($Detail)"})"
|
|
} else {
|
|
$mark = if ($NoColor) { "[PASS]" } else { [char]0x2713 }
|
|
$msg = " $mark $Name"
|
|
if ($Detail) { $msg += " - $Detail" }
|
|
Write-Color $msg "Green"
|
|
}
|
|
}
|
|
|
|
function Record-Fail {
|
|
param([string]$Name, [string]$Detail = "")
|
|
$script:Fail++
|
|
$script:Total++
|
|
$script:Results += [PSCustomObject]@{ Status="FAIL"; Name=$Name; Detail=$Detail }
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "not ok $($script:Total) - $Name"
|
|
if ($Detail) { Write-Host " # $Detail" }
|
|
} else {
|
|
$mark = if ($NoColor) { "[FAIL]" } else { [char]0x2717 }
|
|
$msg = " $mark $Name"
|
|
if ($Detail) { $msg += " - $Detail" }
|
|
Write-Color $msg "Red"
|
|
}
|
|
}
|
|
|
|
function Record-Skip {
|
|
param([string]$Name, [string]$Reason = "")
|
|
$script:Skip++
|
|
$script:Total++
|
|
$script:Results += [PSCustomObject]@{ Status="SKIP"; Name=$Name; Detail=$Reason }
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "ok $($script:Total) - $Name # SKIP $Reason"
|
|
} else {
|
|
$mark = if ($NoColor) { "[SKIP]" } else { [char]0x2298 }
|
|
$msg = " $mark $Name"
|
|
if ($Reason) { $msg += " - $Reason" }
|
|
Write-Color $msg "Yellow"
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# HELPERS
|
|
# ============================================================================
|
|
|
|
function Test-CommandExists {
|
|
param([string]$Command)
|
|
$null -ne (Get-Command $Command -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
function Write-Section {
|
|
param([string]$Name)
|
|
if ($OutputFormat -eq "text") {
|
|
Write-Host ""
|
|
Write-Color $Name "White"
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# TESTS
|
|
# ============================================================================
|
|
|
|
# -- 1. Windows Server Backup Feature ---------------------------------------
|
|
|
|
function Test-BackupFeature {
|
|
Write-Section "Backup Feature"
|
|
|
|
if (Test-CommandExists "Get-WindowsFeature") {
|
|
try {
|
|
$feature = Get-WindowsFeature -Name Windows-Server-Backup -ErrorAction Stop
|
|
if ($feature.Installed) {
|
|
Record-Pass "Windows Server Backup feature installed"
|
|
} else {
|
|
Record-Fail "Windows Server Backup feature installed" "feature not installed"
|
|
}
|
|
} catch {
|
|
Record-Fail "Windows Server Backup feature installed" $_.Exception.Message
|
|
}
|
|
} else {
|
|
# Workstation or older OS — check for wbadmin directly
|
|
if (Test-CommandExists "wbadmin") {
|
|
Record-Pass "Windows Server Backup feature installed" "wbadmin available"
|
|
} else {
|
|
Record-Skip "Windows Server Backup feature installed" "Get-WindowsFeature not available"
|
|
}
|
|
}
|
|
}
|
|
|
|
# -- 2. Backup Scheduled Task -----------------------------------------------
|
|
|
|
function Test-BackupScheduledTask {
|
|
Write-Section "Scheduled Task"
|
|
|
|
try {
|
|
$task = Get-ScheduledTask -TaskName "Microsoft-Windows-WindowsBackup" -TaskPath "\Microsoft\Windows\WindowsBackup\" -ErrorAction SilentlyContinue
|
|
if (-not $task) {
|
|
# Try alternative task name
|
|
$task = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object {
|
|
$_.TaskPath -match "WindowsBackup" -or $_.TaskName -match "WindowsBackup"
|
|
} | Select-Object -First 1
|
|
}
|
|
|
|
if ($task) {
|
|
if ($task.State -eq "Ready" -or $task.State -eq "Running") {
|
|
Record-Pass "Backup scheduled task enabled" "state: $($task.State)"
|
|
} else {
|
|
Record-Fail "Backup scheduled task enabled" "state: $($task.State)"
|
|
}
|
|
} else {
|
|
Record-Fail "Backup scheduled task enabled" "no backup scheduled task found"
|
|
}
|
|
} catch {
|
|
Record-Fail "Backup scheduled task enabled" $_.Exception.Message
|
|
}
|
|
}
|
|
|
|
# -- 3. Last Backup Status --------------------------------------------------
|
|
|
|
function Test-LastBackupStatus {
|
|
Write-Section "Backup Status"
|
|
|
|
if (-not (Test-CommandExists "wbadmin")) {
|
|
Record-Skip "Last backup status" "wbadmin not available"
|
|
return
|
|
}
|
|
|
|
try {
|
|
$output = wbadmin get versions 2>&1 | Out-String
|
|
if ($output -match "Backup time:\s*(.+)" -or $output -match "Version identifier:\s*(.+)") {
|
|
Record-Pass "Last backup status" "completed successfully"
|
|
} elseif ($output -match "no previous backups" -or $output -match "There are no versions") {
|
|
Record-Fail "Last backup status" "no backups found"
|
|
} else {
|
|
Record-Fail "Last backup status" "could not parse wbadmin output"
|
|
}
|
|
} catch {
|
|
Record-Fail "Last backup status" "wbadmin error - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# -- 4. Backup Age ----------------------------------------------------------
|
|
|
|
function Test-BackupAge {
|
|
if (-not (Test-CommandExists "wbadmin")) {
|
|
Record-Skip "Backup age" "wbadmin not available"
|
|
return
|
|
}
|
|
|
|
try {
|
|
$output = wbadmin get versions 2>&1 | Out-String
|
|
$timestamps = [regex]::Matches($output, "Backup time:\s*(.+)")
|
|
|
|
if ($timestamps.Count -eq 0) {
|
|
Record-Fail "Backup age" "no backup timestamps found"
|
|
return
|
|
}
|
|
|
|
# Get the most recent backup time
|
|
$latestStr = $timestamps[$timestamps.Count - 1].Groups[1].Value.Trim()
|
|
$latestTime = [datetime]::Parse($latestStr)
|
|
$ageHours = [math]::Floor(((Get-Date) - $latestTime).TotalHours)
|
|
|
|
if ($ageHours -le $MaxBackupAgeHours) {
|
|
Record-Pass "Backup age" "${ageHours}h < ${MaxBackupAgeHours}h threshold"
|
|
} else {
|
|
Record-Fail "Backup age" "${ageHours}h exceeds ${MaxBackupAgeHours}h threshold"
|
|
}
|
|
} catch {
|
|
Record-Fail "Backup age" "could not determine backup age - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# -- 5. Backup Target Accessible --------------------------------------------
|
|
|
|
function Test-BackupTargetAccessible {
|
|
Write-Section "Backup Target"
|
|
|
|
$target = $BackupTarget
|
|
|
|
# Auto-detect if not specified
|
|
if (-not $target -and (Test-CommandExists "wbadmin")) {
|
|
try {
|
|
$policyOutput = wbadmin get policy 2>&1 | Out-String
|
|
if ($policyOutput -match "Backup Target:\s*(.+)") {
|
|
$target = $Matches[1].Trim()
|
|
Write-Verbose "Auto-detected backup target: $target"
|
|
}
|
|
} catch {
|
|
Write-Verbose "Could not auto-detect backup target from policy"
|
|
}
|
|
}
|
|
|
|
if (-not $target) {
|
|
Record-Skip "Backup target accessible" "no backup target specified or detected"
|
|
return
|
|
}
|
|
|
|
try {
|
|
if (Test-Path $target -ErrorAction Stop) {
|
|
Record-Pass "Backup target accessible" $target
|
|
} else {
|
|
Record-Fail "Backup target accessible" "$target - not accessible"
|
|
}
|
|
} catch {
|
|
Record-Fail "Backup target accessible" "$target - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# -- 6. VSS Writers Healthy -------------------------------------------------
|
|
|
|
function Test-VSSWriters {
|
|
Write-Section "VSS"
|
|
|
|
if (-not (Test-CommandExists "vssadmin")) {
|
|
Record-Skip "VSS writers healthy" "vssadmin not available"
|
|
return
|
|
}
|
|
|
|
try {
|
|
$output = vssadmin list writers 2>&1 | Out-String
|
|
$writerBlocks = [regex]::Matches($output, "Writer name:\s*'([^']+)'.+?State:\s*\[(\d+)\]\s*(\w+)", [System.Text.RegularExpressions.RegexOptions]::Singleline)
|
|
|
|
if ($writerBlocks.Count -eq 0) {
|
|
Record-Fail "VSS writers healthy" "could not parse writer state"
|
|
return
|
|
}
|
|
|
|
$failedWriters = @()
|
|
foreach ($match in $writerBlocks) {
|
|
$writerName = $match.Groups[1].Value
|
|
$stateNum = $match.Groups[2].Value
|
|
$stateText = $match.Groups[3].Value
|
|
if ($stateText -ne "Stable" -and $stateText -ne "Waiting") {
|
|
$failedWriters += "$writerName ($stateText)"
|
|
}
|
|
}
|
|
|
|
if ($failedWriters.Count -eq 0) {
|
|
Record-Pass "VSS writers healthy" "$($writerBlocks.Count) writers, 0 failed"
|
|
} else {
|
|
Record-Fail "VSS writers healthy" "$($failedWriters.Count) failed: $($failedWriters -join ', ')"
|
|
}
|
|
} catch {
|
|
Record-Fail "VSS writers healthy" "vssadmin error - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# -- 7. VSS Shadow Copies Exist ---------------------------------------------
|
|
|
|
function Test-VSSShadowCopies {
|
|
if (-not (Test-CommandExists "vssadmin")) {
|
|
Record-Skip "VSS shadow copies exist" "vssadmin not available"
|
|
return
|
|
}
|
|
|
|
try {
|
|
$output = vssadmin list shadows 2>&1 | Out-String
|
|
$shadowCount = ([regex]::Matches($output, "Shadow Copy ID:")).Count
|
|
|
|
if ($shadowCount -gt 0) {
|
|
Record-Pass "VSS shadow copies exist" "$shadowCount shadow copies"
|
|
} elseif ($output -match "No items found") {
|
|
Record-Fail "VSS shadow copies exist" "no shadow copies found"
|
|
} else {
|
|
Record-Fail "VSS shadow copies exist" "no shadow copies detected"
|
|
}
|
|
} catch {
|
|
Record-Fail "VSS shadow copies exist" "vssadmin error - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# -- 8. System State Backup -------------------------------------------------
|
|
|
|
function Test-SystemStateBackup {
|
|
Write-Section "System State"
|
|
|
|
if (-not (Test-CommandExists "wbadmin")) {
|
|
Record-Skip "System state backup present" "wbadmin not available"
|
|
return
|
|
}
|
|
|
|
try {
|
|
$output = wbadmin get versions 2>&1 | Out-String
|
|
if ($output -match "Can recover:\s*.*System State" -or $output -match "systemStateBackup" -or $output -match "System State") {
|
|
Record-Pass "System state backup present"
|
|
} elseif ($output -match "no previous backups" -or $output -match "There are no versions") {
|
|
Record-Fail "System state backup present" "no backups found"
|
|
} else {
|
|
Record-Fail "System state backup present" "system state not included in backups"
|
|
}
|
|
} catch {
|
|
Record-Fail "System state backup present" "wbadmin error - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# -- 9. Backup Disk Space ---------------------------------------------------
|
|
|
|
function Test-BackupDiskSpace {
|
|
Write-Section "Disk Space"
|
|
|
|
$target = $BackupTarget
|
|
if (-not $target -and (Test-CommandExists "wbadmin")) {
|
|
try {
|
|
$policyOutput = wbadmin get policy 2>&1 | Out-String
|
|
if ($policyOutput -match "Backup Target:\s*(.+)") { $target = $Matches[1].Trim() }
|
|
} catch {}
|
|
}
|
|
|
|
if (-not $target) { Record-Skip "Backup disk space" "no backup target specified or detected"; return }
|
|
|
|
try {
|
|
if ($target -match "^([A-Za-z]):\\") {
|
|
$drive = Get-PSDrive -Name $Matches[1] -ErrorAction Stop
|
|
$freeGB = [math]::Round($drive.Free / 1GB, 0)
|
|
if ($freeGB -ge 10) { Record-Pass "Backup disk space" "$freeGB GB free on $($Matches[1]):" }
|
|
elseif ($freeGB -ge 1) { Record-Pass "Backup disk space" "$freeGB GB free on $($Matches[1]): (low)" }
|
|
else { Record-Fail "Backup disk space" "$freeGB GB free on $($Matches[1]): — critically low" }
|
|
} elseif ($target -match "^\\\\") {
|
|
if (Test-Path $target -ErrorAction Stop) { Record-Pass "Backup disk space" "UNC target accessible (space check not available)" }
|
|
else { Record-Fail "Backup disk space" "UNC target not accessible" }
|
|
} else {
|
|
Record-Skip "Backup disk space" "cannot determine volume from target: $target"
|
|
}
|
|
} catch {
|
|
Record-Fail "Backup disk space" $_.Exception.Message
|
|
}
|
|
}
|
|
|
|
# -- 10. Backup Event Log ---------------------------------------------------
|
|
|
|
function Test-BackupEventLog {
|
|
Write-Section "Event Log"
|
|
|
|
try {
|
|
$logName = "Microsoft-Windows-Backup"
|
|
$cutoff = (Get-Date).AddHours(-24)
|
|
|
|
$errors = Get-WinEvent -FilterHashtable @{
|
|
LogName = "$logName/Operational"
|
|
Level = 2 # Error
|
|
StartTime = $cutoff
|
|
} -ErrorAction SilentlyContinue
|
|
|
|
if (-not $errors) {
|
|
# Try alternative log name
|
|
$errors = Get-WinEvent -FilterHashtable @{
|
|
ProviderName = "Microsoft-Windows-Backup"
|
|
Level = 2
|
|
StartTime = $cutoff
|
|
} -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
$errorCount = if ($errors) { ($errors | Measure-Object).Count } else { 0 }
|
|
|
|
if ($errorCount -eq 0) {
|
|
Record-Pass "Backup event log clean" "0 errors in 24h"
|
|
} else {
|
|
$latestMsg = $errors[0].Message
|
|
if ($latestMsg.Length -gt 80) { $latestMsg = $latestMsg.Substring(0, 80) + "..." }
|
|
Record-Fail "Backup event log clean" "$errorCount error(s) in 24h — latest: $latestMsg"
|
|
}
|
|
} catch {
|
|
if ($_.Exception.Message -match "No events were found" -or $_.Exception.Message -match "could not be found") {
|
|
Record-Pass "Backup event log clean" "0 errors in 24h"
|
|
} else {
|
|
Record-Skip "Backup event log clean" "cannot access backup event log"
|
|
}
|
|
}
|
|
}
|
|
|
|
# -- 11. SQL Server Backup Age ----------------------------------------------
|
|
|
|
function Test-SQLBackupAge {
|
|
Write-Section "SQL Server"
|
|
|
|
# Check if SQL Server is installed
|
|
$sqlService = Get-Service -Name "MSSQLSERVER" -ErrorAction SilentlyContinue
|
|
if (-not $sqlService) {
|
|
$sqlService = Get-Service -ErrorAction SilentlyContinue | Where-Object {
|
|
$_.Name -match "^MSSQL\$" -or $_.Name -eq "MSSQLSERVER"
|
|
} | Select-Object -First 1
|
|
}
|
|
|
|
if (-not $sqlService) {
|
|
Record-Skip "SQL Server backup age" "SQL Server not installed"
|
|
return
|
|
}
|
|
|
|
$query = @"
|
|
SELECT TOP 1
|
|
bs.database_name,
|
|
bs.backup_finish_date,
|
|
DATEDIFF(HOUR, bs.backup_finish_date, GETDATE()) AS age_hours
|
|
FROM msdb.dbo.backupset bs
|
|
ORDER BY bs.backup_finish_date DESC
|
|
"@
|
|
|
|
# Try Invoke-Sqlcmd, then fall back to sqlcmd
|
|
$ageH = $null
|
|
$dbName = ""
|
|
|
|
if (Test-CommandExists "Invoke-Sqlcmd") {
|
|
try {
|
|
$result = Invoke-Sqlcmd -Query $query -ServerInstance "localhost" -ErrorAction Stop
|
|
if ($result) { $ageH = $result.age_hours; $dbName = $result.database_name }
|
|
else { Record-Fail "SQL Server backup age" "no SQL backups found in msdb"; return }
|
|
} catch { Write-Verbose "Invoke-Sqlcmd failed: $($_.Exception.Message)" }
|
|
}
|
|
|
|
if ($null -eq $ageH -and (Test-CommandExists "sqlcmd")) {
|
|
try {
|
|
$output = sqlcmd -S localhost -Q $query -h -1 -W 2>&1 | Out-String
|
|
if ($output -match "(\d+)\s*$") { $ageH = [int]$Matches[1] }
|
|
else { Record-Fail "SQL Server backup age" "could not parse sqlcmd output"; return }
|
|
} catch { Write-Verbose "sqlcmd failed: $($_.Exception.Message)" }
|
|
}
|
|
|
|
if ($null -ne $ageH) {
|
|
$detail = "${ageH}h$(if($dbName){" ($dbName)"})"
|
|
if ($ageH -le $MaxBackupAgeHours) { Record-Pass "SQL Server backup age" $detail }
|
|
else { Record-Fail "SQL Server backup age" "$detail exceeds ${MaxBackupAgeHours}h threshold" }
|
|
} else {
|
|
Record-Skip "SQL Server backup age" "SQL Server present but query tools unavailable"
|
|
}
|
|
}
|
|
|
|
# -- 12. Backup Schedule Configured ------------------------------------------
|
|
|
|
function Test-BackupSchedule {
|
|
Write-Section "Schedule"
|
|
|
|
if (-not (Test-CommandExists "wbadmin")) {
|
|
Record-Skip "Backup schedule configured" "wbadmin not available"
|
|
return
|
|
}
|
|
|
|
try {
|
|
$output = wbadmin get policy 2>&1 | Out-String
|
|
|
|
if ($output -match "Schedule:" -or $output -match "Times of day:") {
|
|
Record-Pass "Backup schedule configured"
|
|
} elseif ($output -match "no current backup policy" -or $output -match "There is no currently-set") {
|
|
Record-Fail "Backup schedule configured" "no backup policy configured"
|
|
} else {
|
|
Record-Fail "Backup schedule configured" "could not confirm schedule in policy output"
|
|
}
|
|
} catch {
|
|
Record-Fail "Backup schedule configured" "wbadmin error - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# -- 13. Bare Metal Recovery ------------------------------------------------
|
|
|
|
function Test-BareMetalRecovery {
|
|
Write-Section "Bare Metal Recovery"
|
|
|
|
if (-not (Test-CommandExists "wbadmin")) {
|
|
Record-Skip "Bare metal recovery components included" "wbadmin not available"
|
|
return
|
|
}
|
|
|
|
try {
|
|
# Check policy for BMR
|
|
$policyOutput = wbadmin get policy 2>&1 | Out-String
|
|
$hasBMRPolicy = $policyOutput -match "bare metal recovery" -or $policyOutput -match "baremetal" -or $policyOutput -match "BMR"
|
|
|
|
# Check versions for BMR
|
|
$versionsOutput = wbadmin get versions 2>&1 | Out-String
|
|
$hasBMRVersion = $versionsOutput -match "Bare Metal Recovery" -or $versionsOutput -match "Can recover:.*Full Server"
|
|
|
|
if ($hasBMRPolicy -or $hasBMRVersion) {
|
|
Record-Pass "Bare metal recovery components included"
|
|
} elseif ($versionsOutput -match "no previous backups" -or $versionsOutput -match "There are no versions") {
|
|
Record-Fail "Bare metal recovery components included" "no backups found to check"
|
|
} else {
|
|
Record-Fail "Bare metal recovery components included" "BMR not detected in backup policy or versions"
|
|
}
|
|
} catch {
|
|
Record-Fail "Bare metal recovery components included" "wbadmin error - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# OUTPUT
|
|
# ============================================================================
|
|
|
|
function Write-Header {
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "TAP version 13"
|
|
} else {
|
|
Write-Host ""
|
|
Write-Color "Windows Backup Smoke Tests" "White"
|
|
Write-Host "Host: $($env:COMPUTERNAME)"
|
|
Write-Host "Target: $(if ($BackupTarget) { $BackupTarget } else { '(auto-detect)' })"
|
|
Write-Host "Time: $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
|
|
}
|
|
}
|
|
|
|
function Write-Summary {
|
|
$duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)
|
|
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "1..$($script:Total)"
|
|
Write-Host "# pass $($script:Pass)"
|
|
Write-Host "# fail $($script:Fail)"
|
|
Write-Host "# skip $($script:Skip)"
|
|
} else {
|
|
Write-Host ""
|
|
$separator = [string]::new([char]0x2500, 40)
|
|
Write-Color $separator "White"
|
|
Write-Color "Summary $($env:COMPUTERNAME)" "White"
|
|
|
|
$summaryLine = " $($script:Pass) passed $($script:Fail) failed $($script:Skip) skipped (${duration}s)"
|
|
Write-Host $summaryLine
|
|
Write-Color $separator "White"
|
|
|
|
if ($script:Fail -eq 0) {
|
|
Write-Color "All tests passed." "Green"
|
|
} else {
|
|
Write-Color "$($script:Fail) test(s) failed." "Red"
|
|
}
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# MAIN
|
|
# ============================================================================
|
|
|
|
Write-Header
|
|
|
|
# Run all tests
|
|
Test-BackupFeature
|
|
Test-BackupScheduledTask
|
|
Test-LastBackupStatus
|
|
Test-BackupAge
|
|
Test-BackupTargetAccessible
|
|
Test-VSSWriters
|
|
Test-VSSShadowCopies
|
|
Test-SystemStateBackup
|
|
Test-BackupDiskSpace
|
|
Test-BackupEventLog
|
|
Test-SQLBackupAge
|
|
Test-BackupSchedule
|
|
Test-BareMetalRecovery
|
|
|
|
Write-Summary
|
|
|
|
if ($script:Fail -eq 0) { exit 0 } else { exit 1 }
|