Build a Windows Patch Evidence Reporting Workflow with PowerShell and Scheduled Scans
Build a scheduled Windows patch-evidence report with PowerShell and Get-HotFix, then validate the generated CSV and scheduled task.
Expected Outcome
You will have a repeatable local evidence workflow that exports installed CBS/QFE hotfix records to CSV on a schedule, plus a clear validation and rollback path. The report is evidence you can use in a broader patch-assessment process; it is not a compliance verdict by itself.
Assumptions
Windows 10, Windows 11, or Windows Server with Windows PowerShell 5.1 or later
An elevated PowerShell session on the target machine
Permission to create C:\Scripts and C:\Reports and to create a local scheduled task
An approved patch baseline or management source if you intend to make a compliance determination beyond this local evidence report
Bill of Materials
Windows PowerShell
Get-HotFix for local CBS/QFE evidence
Windows Task Scheduler (schtasks.exe)
CSV output for review or downstream comparison
Build Steps
- Prepare the local script and report directories
Create the directories before writing or running the script. These commands are directly runnable from an elevated PowerShell session.
Changes system state: review before running
New-Item -Path 'C:\Scripts' -ItemType Directory -Force | Out-Null New-Item -Path 'C:\Reports' -ItemType Directory -Force | Out-Null Get-Item 'C:\Scripts','C:\Reports'
- Create the installed-hotfix evidence script
Write a small script that inventories the hotfix records Get-HotFix can see and exports consistent columns. Get-HotFix is useful evidence, but Microsoft documents that it is backed by Win32_QuickFixEngineering and does not represent every update source.
Read-only command: verify target and scope
$script = @' $reportPath = 'C:\Reports\InstalledHotFixEvidence.csv' Get-HotFix |
Manual or UI step
Sort-Object InstalledOn -Descending |
Writes local output artifacts: review the output path before running
Select-Object @{Name='ComputerName';Expression={$env:COMPUTERNAME}}, HotFixID, Description, InstalledBy, InstalledOn | Export-Csv -Path $reportPath -NoTypeInformation -Encoding UTF8Manual or UI step
'@
Changes system state: review before running
Set-Content -Path 'C:\Scripts\InstalledHotFixEvidence.ps1' -Value $script -Encoding UTF8 Get-Content 'C:\Scripts\InstalledHotFixEvidence.ps1'
- Run the script interactively before scheduling it
Prove the script works in your current security context before adding Task Scheduler to the troubleshooting surface.
Review before running: verify target, scope, and execution context
& 'C:\Scripts\InstalledHotFixEvidence.ps1' Get-Item 'C:\Reports\InstalledHotFixEvidence.csv' | Select-Object FullName, Length, LastWriteTime Import-Csv 'C:\Reports\InstalledHotFixEvidence.csv' | Select-Object -First 10 | Format-Table -AutoSize
- Create the daily scheduled task
Create a daily 09:00 task that runs the tested script. This example uses the current local user context. If your production design requires a service account or SYSTEM, define and test that execution context explicitly rather than assuming it is equivalent.
Changes system state: review before running
schtasks /create /tn "InstalledHotFixEvidence" /tr "powershell.exe -NoProfile -File C:\Scripts\InstalledHotFixEvidence.ps1" /sc daily /st 09:00 /f schtasks /query /tn "InstalledHotFixEvidence" /fo LIST /v
- Run the scheduled task on demand
Start the scheduled task immediately. The schtasks run operation uses the account and action saved in the task, which makes this a better validation of the scheduled execution context than rerunning the script interactively.
Changes system state: review before running
schtasks /run /tn "InstalledHotFixEvidence" Start-Sleep -Seconds 5 Get-Item 'C:\Reports\InstalledHotFixEvidence.csv' | Select-Object FullName, Length, LastWriteTime
- Validate the CSV against the same native evidence source
Compare the exported rows with live Get-HotFix output. The CSV should contain ComputerName, HotFixID, Description, InstalledBy, and InstalledOn. Do not expect a compliance status column because this workflow has no intended-baseline comparison.
Read-only command: verify target and scope
Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 HotFixID, Description, InstalledBy, InstalledOn | Format-Table -AutoSize Import-Csv 'C:\Reports\InstalledHotFixEvidence.csv' | Select-Object -First 10 ComputerName, HotFixID, Description, InstalledBy, InstalledOn | Format-Table -AutoSize
- Capture task configuration evidence
Keep the task definition and verbose task state with the report if you need ticket or handoff evidence.
Read-only command: verify target and scope
schtasks /query /tn "InstalledHotFixEvidence" /v /fo LIST schtasks /query /tn "InstalledHotFixEvidence" /xml > C:\Reports\InstalledHotFixEvidenceTask.xml
- Rollback the scheduled task if the workflow is not adopted
This is a destructive cleanup command: it deletes the scheduled task but does not delete the script or report files. Run it only when you intend to remove the schedule.
Destructive: review before running
schtasks /delete /tn "InstalledHotFixEvidence" /f Get-ScheduledTask -TaskName 'InstalledHotFixEvidence' -ErrorAction SilentlyContinue
Validation
After the interactive run, C:\Reports\InstalledHotFixEvidence.csv should exist, have a recent LastWriteTime, and contain ComputerName, HotFixID, Description, InstalledBy, and InstalledOn columns.
After schtasks /run, the CSV LastWriteTime should advance. If it does not, inspect the task's Last Run Time and Last Result with schtasks /query /tn "InstalledHotFixEvidence" /fo LIST /v before changing the script.
Treat the CSV as installed-hotfix evidence only. A true compliance result requires an intended baseline and an explicit comparison that accounts for collection coverage and evidence gaps.
Troubleshooting
If the interactive script fails, fix the script, path, or local permissions before troubleshooting Task Scheduler.
If the task runs but the CSV timestamp does not advance, verify the saved task action, execution account, and that account's access to C:\Scripts and C:\Reports.
If Get-HotFix does not list an update you expect, do not infer that the update is absent. Microsoft documents that Win32_QuickFixEngineering returns only updates supplied by Component Based Servicing (CBS), not every Windows Update or MSI-delivered update.
If you later add remote targets, validate WinRM, firewall policy, authentication, and per-host collection failures separately so unreachable systems do not silently disappear from the evidence set.
Cleanup or Rollback
Use the dedicated rollback step above to delete the InstalledHotFixEvidence scheduled task if the workflow is not adopted.
Archive any evidence required for a ticket or change record before removing C:\Scripts\InstalledHotFixEvidence.ps1 or C:\Reports\InstalledHotFixEvidence.csv.
Do not delete the local evidence solely because it disagrees with another patch source; reconcile the source, scope, collection time, and baseline first.
Next Improvements
To turn this evidence collector into real compliance reporting, add an authoritative intended baseline and calculate coverage separately from assessed compliance so collection failures remain visible.
If reports must be distributed, use an organization-approved mail or API path. Microsoft marks Send-MailMessage obsolete and recommends against using it for new automation; Exchange Online environments can use Microsoft Graph PowerShell such as Send-MgUserMail.
If you evaluate community modules such as PSWindowsUpdate, treat them as third-party dependencies and validate their source, version, execution context, and output before making them part of a compliance process.
