Storage and BackupBackup PlatformsIntermediate90 minutesLab

PowerShell Home Lab Toolkit for Backup Freshness, Service Health, and Daily Status

Build a small PowerShell operations toolkit that records backup-file freshness, checks selected Windows services, writes durable daily evidence, and runs on a controlled schedule.

Last reviewed8/18/2026
Windows share backup auditsbackup freshness reporting
PowerShellWindows Task SchedulerFile-based backup evidenceCSV and JSON reporting

Expected Outcome

A parameterized PowerShell script that inventories recent backup artifacts without claiming restore verification, records selected service states without changing them by default, exports a timestamped status report, and can run daily under a deliberately chosen Task Scheduler identity.

Assumptions

  • Windows 10/11 or Windows Server with Windows PowerShell 5.1 or PowerShell 7 available for the script logic.

  • Read access to the backup directory or share whose file freshness you want to inspect.

  • A defined freshness threshold that matches the expected backup cadence; a seven-day threshold is only an example.

  • A short list of Windows service names that matter in your lab and an understanding of whether the scheduled task identity can query them.

  • An output directory where the scheduled task identity can create status files.

Bill of Materials

  • A script directory such as C:\Scripts.

  • A backup folder or UNC path containing backup artifacts that can be dated reliably.

  • A report directory such as C:\OpsEvidence\HomeLabStatus.

  • Task Scheduler or schtasks.exe for recurring execution.

Build Steps

  1. Define the evidence scope

    Choose the backup location, filename pattern, freshness threshold, services, and report directory before writing the script. File age can show whether expected backup artifacts are arriving; it does not prove that those artifacts are valid or restorable.

    Configuration or code example: review values for your environment

    $BackupPath = 'C:\Backups'
    $BackupFilter = '*.bak'
    $MaxAgeHours = 24
    $ServiceNames = @('Spooler')
    $ReportPath = 'C:\OpsEvidence\HomeLabStatus'
  2. Collect backup freshness evidence

    Inventory matching files and calculate age from LastWriteTime. Preserve the newest matching artifact as evidence and report a missing-artifact state separately from an old-artifact state.

    Configuration or code example: review values for your environment

    $Now = Get-Date
    $BackupFiles = Get-ChildItem -Path $BackupPath -Filter $BackupFilter -File -ErrorAction Stop | Sort-Object LastWriteTime -Descending
    $NewestBackup = $BackupFiles | Select-Object -First 1
    $BackupEvidence = if ($null -eq $NewestBackup) { [pscustomobject]@{ Status='Missing'; Path=$BackupPath; File=$null; LastWriteTime=$null; AgeHours=$null; ThresholdHours=$MaxAgeHours } } else { $AgeHours = [math]::Round(($Now - $NewestBackup.LastWriteTime).TotalHours, 1); [pscustomobject]@{ Status=if ($AgeHours -le $MaxAgeHours) { 'Fresh' } else { 'Stale' }; Path=$BackupPath; File=$NewestBackup.FullName; LastWriteTime=$NewestBackup.LastWriteTime; AgeHours=$AgeHours; ThresholdHours=$MaxAgeHours } }
  3. Collect service health without changing state

    Query only the services you deliberately listed. The default workflow is observation, not automatic remediation, so an unexpected stopped service remains visible for review instead of being silently restarted.

    Configuration or code example: review values for your environment

    $ServiceEvidence = foreach ($Name in $ServiceNames) { try { $Service = Get-Service -Name $Name -ErrorAction Stop; [pscustomobject]@{ Name=$Service.Name; DisplayName=$Service.DisplayName; Status=[string]$Service.Status; Found=$true } } catch { [pscustomobject]@{ Name=$Name; DisplayName=$null; Status='NotFound'; Found=$false } } }
  4. Write a durable daily status record

    Create the evidence directory, combine backup and service status with a collection timestamp, and write both human-readable and structured outputs. Keep reporting local first; integrate mail, webhooks, or monitoring only through an organization-supported path after the evidence is trustworthy.

    Changes system state: review before running

    New-Item -ItemType Directory -Path $ReportPath -Force | Out-Null
    $Stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
    $Summary = [pscustomobject]@{ CollectedAt=(Get-Date).ToString('o'); BackupStatus=$BackupEvidence.Status; BackupFile=$BackupEvidence.File; BackupAgeHours=$BackupEvidence.AgeHours; UnhealthyServices=@($ServiceEvidence | Where-Object Status -ne 'Running').Count }
    $Summary | ConvertTo-Json -Depth 4 | Set-Content -Path (Join-Path $ReportPath "status-$Stamp.json") -Encoding UTF8
    $ServiceEvidence | Export-Csv -Path (Join-Path $ReportPath "services-$Stamp.csv") -NoTypeInformation -Encoding UTF8
    $Summary | Format-List | Out-String | Set-Content -Path (Join-Path $ReportPath "status-$Stamp.txt") -Encoding UTF8
  5. Test the script interactively before scheduling it

    Save the working code as C:\Scripts\HomeLabStatus.ps1, run it in the same security context you intend to schedule, and inspect the newest outputs. For a UNC backup path, verify that the chosen account can reach the share without depending on an interactive drive mapping.

    Review before running: verify target, scope, and execution context

    powershell.exe -NoProfile -File C:\Scripts\HomeLabStatus.ps1
    Get-ChildItem C:\OpsEvidence\HomeLabStatus | Sort-Object LastWriteTime -Descending | Select-Object -First 6 Name, LastWriteTime, Length
  6. Create one daily scheduled task

    Schedule the single status script rather than three loosely coupled tasks. The account that creates/runs the task must have the permissions required by the backup path, services, script, and report directory. Do not embed a password in source control or documentation.

    Changes system state: review before running

    schtasks /create /tn "OpsStack\HomeLabStatus" /tr "powershell.exe -NoProfile -File C:\Scripts\HomeLabStatus.ps1" /sc daily /st 09:00 /f
    schtasks /query /tn "OpsStack\HomeLabStatus" /v /fo list
  7. Run the scheduled task on demand and validate its context

    Use the saved task definition for an immediate test. schtasks /run uses the program and account already stored in the task, so this test is useful for catching differences between an interactive shell and the scheduled execution context.

    Changes system state: review before running

    schtasks /run /tn "OpsStack\HomeLabStatus"
    Start-Sleep -Seconds 5
    schtasks /query /tn "OpsStack\HomeLabStatus" /v /fo list
    Get-ChildItem C:\OpsEvidence\HomeLabStatus | Sort-Object LastWriteTime -Descending | Select-Object -First 6 Name, LastWriteTime, Length
  8. Keep remediation separate from monitoring

    If a stopped service is confirmed to be one that should be running, remediate it explicitly after reviewing dependencies and the failure context. Do not add blanket automatic Start-Service behavior to the status collector unless you have defined which services are safe to restart and how restart failures are escalated.

    Changes system state: review before running

    Get-Service -Name '<SERVICE_NAME>' | Select-Object Name, Status, StartType
    Start-Service -Name '<SERVICE_NAME>'
  9. Add notifications only through a supported path

    If daily or exception notifications are needed, route the structured evidence into a supported monitoring, webhook, ticketing, or mail mechanism. Microsoft marks Send-MailMessage obsolete and recommends against using it for new automation because it does not guarantee secure SMTP connections.

Validation

  • Force a stale-data test by setting a threshold below the age of the newest test artifact and confirm the report changes to Stale without deleting or modifying the backup file.

  • Use a deliberately nonexistent service name in the test list and confirm the report records NotFound rather than terminating the whole collection.

  • Run the scheduled task on demand and confirm it creates new output files under the scheduled task's saved execution context.

    Review before running: verify target, scope, and execution context

    schtasks /run /tn "OpsStack\HomeLabStatus"
    schtasks /query /tn "OpsStack\HomeLabStatus" /v /fo list
  • Open the newest JSON and CSV files and reconcile the backup state and service counts with a manual check of the source path and Get-Service output.

  • Document separately how restore verification is performed for the protected workload; a Fresh file result is not a restore test.

Troubleshooting

  • If the interactive run succeeds but the scheduled run fails, compare the task account, working paths, share permissions, and whether the script depends on an interactive drive mapping.

  • If the backup path is empty unexpectedly, verify the filter and path independently before increasing the freshness threshold.

  • If a service query returns AccessDenied or NotFound, validate the exact service name and execution context before adding broader privileges.

  • If schtasks reports a task error, query the task verbosely and inspect Task Scheduler operational history before recreating it.

    Read-only command: verify target and scope

    schtasks /query /tn "OpsStack\HomeLabStatus" /v /fo list

Cleanup or Rollback

  • Remove the scheduled task if the Lab is no longer needed.

    Warning: Deleting the task stops future collection; preserve any evidence files you still need first.

    Destructive: review before running

    schtasks /delete /tn "OpsStack\HomeLabStatus" /f
  • Remove only the Lab's generated evidence directory after deciding that its history is no longer required.

    Warning: This permanently deletes the generated status history at that path.

    Destructive: review before running

    Remove-Item -Path 'C:\OpsEvidence\HomeLabStatus' -Recurse -Force

Next Improvements

  • Send only exception conditions to an organization-approved notification path rather than mailing every successful run.

  • Add retention for old status files after deciding how much evidence history is useful.

  • Add a real restore drill for the backup platform so freshness evidence and recoverability evidence are tracked separately.

  • Move thresholds and monitored service names into a small configuration file once the script behavior is stable.

References

Keep Moving

Build on what you just completed

Continue with a related Lab or return to this build path for a different implementation.