Shore Up
A robot arm placing a fresh tile into a wall of tiles, while a clipboard beside it records each tile that was fitted.
WindowsSecurity

Automate Windows Update Installation and Reporting with PowerShell

Ketan Aagja8 min read
No ratings yet

Before you run this

This guide uses the community PSWindowsUpdate module to check for, install, and log Windows updates on the machine it runs on. The end result is a scheduled job that patches a box unattended and drops a CSV report so you can see what happened.

It needs elevation. Installing updates is a system-level operation. You must run PowerShell as Administrator, and later the scheduled task runs as SYSTEM with highest privileges. Installing the module for all users also requires an elevated session.

Test it first. Read the script before you run it. Run it on a test VM or a single non-production server before you point it at anything that matters. Patching can require a reboot, can occasionally break an application, and — critically — installing an update is not something you casually roll back. Get-WindowsUpdate (the listing step) changes nothing and is safe to run anywhere; Install-WindowsUpdate changes the system. Treat the reboot behaviour with respect: the example below suppresses automatic reboots so you stay in control of when the machine restarts.

Nothing here deletes user data, but installed updates and any reboot they trigger are real changes to a live system. Undo is possible for most updates (see the last section) but is not guaranteed for all of them.

Assumptions

  • Windows Server 2022 (the steps are identical on Server 2019 and on Windows 10/11).
  • Windows PowerShell 5.1, the version shipped in-box. PowerShell 7 works too, but 5.1 is what a scheduled task will find by default.
  • The machine reaches either Windows Update or your internal WSUS. PSWindowsUpdate honours the update source the machine is already configured to use.
  • You have local Administrator rights.

Install the module

PSWindowsUpdate lives on the PowerShell Gallery. Install it once, for all users, from an elevated prompt:

# One-time setup, elevated session
Install-Module -Name PSWindowsUpdate -Scope AllUsers -Force
Import-Module PSWindowsUpdate

# Confirm it loaded and see what it offers
Get-Command -Module PSWindowsUpdate

If this is the first time you have used the Gallery, PowerShell may prompt you to trust the NuGet provider and the PSGallery repository. That is expected. If your environment blocks the public Gallery, download the module on a connected machine and copy it into a module path — but the Gallery is the standard route.

Step 1 — List updates without installing anything

Always start with the read-only view. This queries for applicable updates and installs nothing:

# Read-only: lists available updates, changes nothing
Get-WindowsUpdate -MicrosoftUpdate

-MicrosoftUpdate widens the scope to include Microsoft Update (drivers and other Microsoft products), not just core Windows Update. Drop that switch if you only want Windows OS updates. Look at the KB numbers and titles before you go further — this is your dry run.

Step 2 — Install updates, with reboot under your control

Here is the install command. Read every switch before running it:

# Installs all applicable updates. -AcceptAll accepts prompts/EULAs.
# -IgnoreReboot installs but does NOT reboot automatically.
Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -IgnoreReboot -Verbose
  • -AcceptAll accepts each update so the run is unattended.
  • -IgnoreReboot is the safe default: it installs everything but leaves the machine running so you choose when to restart. The alternative is -AutoReboot, which restarts as soon as an update requires it — only use that on a machine where an unplanned reboot is acceptable.
  • -Verbose gives you a live account of what is happening.

After installing, check whether a reboot is now pending:

# Returns True/False for a pending reboot
Get-WURebootStatus -Silent

Step 3 — A script that installs and writes a report

This is the piece you schedule. It installs updates, then writes a CSV of what the machine has recently applied using the module's history cmdlet. Replace C:\path\to\WULogs with a real directory you control.

#Requires -RunAsAdministrator

$logDir = 'C:\path\to\WULogs'   # <-- replace with your own log folder
if (-not (Test-Path $logDir)) {
    New-Item -Path $logDir -ItemType Directory | Out-Null
}

Import-Module PSWindowsUpdate

# Install updates but leave the reboot decision to you
Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -IgnoreReboot -Verbose

# Write a report from update history
$stamp  = Get-Date -Format 'yyyy-MM-dd_HHmmss'
$report = Join-Path $logDir "WU_$stamp.csv"

Get-WUHistory |
    Select-Object Date, Title, KB, Result |
    Export-Csv -Path $report -NoTypeInformation -Encoding UTF8

# Flag whether a restart is now required
if (Get-WURebootStatus -Silent) {
    "Reboot required after run at $stamp" | Out-File (Join-Path $logDir 'reboot-needed.txt')
}

A note on honesty: the exact property names emitted by these cmdlets can vary by module version. Before you rely on the CSV columns, run Get-WUHistory | Get-Member once on your box and confirm Date, Title, KB, and Result exist — adjust the Select-Object list to match what you see.

If you want the report emailed rather than filed, do it as a separate step. Note that the old Send-MailMessage cmdlet still works in 5.1 but Microsoft has deprecated it; check current Microsoft Learn guidance before building an email pipeline on it.

Step 4 — Schedule it

Save the script as C:\path\to\Install-Updates.ps1, then register a weekly task that runs as SYSTEM. These are the standard built-in ScheduledTasks cmdlets:

$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\path\to\Install-Updates.ps1"'

# Runs every Sunday at 03:00 — change to suit your maintenance window
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 3am

$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' `
    -LogonType ServiceAccount -RunLevel Highest

Register-ScheduledTask -TaskName 'Windows Update - Automated' `
    -Action $action -Trigger $trigger -Principal $principal `
    -Description 'Installs Windows updates and writes a CSV report'

-ExecutionPolicy Bypass applies only to this one invocation and does not change the machine-wide policy.

Verify it worked

  • The task exists and last ran cleanly:

    Get-ScheduledTask -TaskName 'Windows Update - Automated' | Get-ScheduledTaskInfo
    

    Check LastRunTime and LastTaskResult (0 means success).

  • The updates actually installed: look at your CSV in the log folder, or query history directly:

    Get-WUHistory | Select-Object -First 20 Date, Title, KB, Result
    
  • Cross-check against Windows' own record in Settings → Windows Update → Update history, or with Get-HotFix for updates that register as hotfixes.

Undo and rollback

Most updates can be removed by KB number. PSWindowsUpdate provides:

# Removes an update by KB. Test on a non-production machine first.
Remove-WindowsUpdate -KBArticleID KB5030000 -Verbose

Substitute the real KB you want gone. The built-in equivalent is wusa /uninstall /kb:5030000. Not every update is removable — some servicing-stack and cumulative updates cannot be cleanly uninstalled — so do not treat removal as guaranteed. For a fuller reversal, a system restore point or a VM snapshot taken before the run is your reliable safety net.

To stop the automation entirely, remove the scheduled task:

Unregister-ScheduledTask -TaskName 'Windows Update - Automated' -Confirm:$false

For exact parameters and current behaviour of the module, see the PSWindowsUpdate page on the PowerShell Gallery, and consult Microsoft Learn for the built-in Register-ScheduledTask and Get-HotFix cmdlets.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.

Monitor a Windows Service and Auto-Restart It with PowerShell

This guide builds a small watchdog: a PowerShell script that checks one named Windows service, and if it is not running, tries to start it and writes the outcome to a log file and the Windows event log. You then schedule it with Task Scheduler so it runs every few minutes. The script starts a stopped service — that is its whole point — so run it only against a service you actually want kept running. It does not delete or reconfigure anything, and starting a service is reversible (you can stop it again), so there is no destructive, one-way step here.

10 min read

Automate a Daily Failed-Logon (4625) Report with PowerShell

This guide builds a scheduled PowerShell job that reads Event ID 4625 (failed logon) from the Windows Security log for the last 24 hours and writes them to a dated HTML report. It is read-only — it queries the event log and creates a report file. It does not change auditing policy, delete events, or touch accounts.

9 min read

Automate Windows Firewall Rule Deployment with PowerShell and netsh

This guide builds and deploys Windows Defender Firewall rules from a table (a CSV), using the NetSecurity PowerShell module, with netsh advfirewall shown as the older equivalent. The goal is a repeatable, idempotent way to push the same rule set to one host or many, instead of clicking through wf.msc on each box.

9 min read

Automate New-User Onboarding in Active Directory

This guide builds a PowerShell script that onboards one new employee in four steps: it creates an Active Directory user account , adds them to security groups , provisions an on-premises Exchange mailbox , and creates their home folder on a file server and sets NTFS permissions . The purpose is to replace the error-prone click-through in Active Directory Users and Computers with one repeatable, reviewable run.

10 min read