The full patch run as seven steps: check the uplink, check administrator rights, make sure the NuGet provider and the PSWindowsUpdate module are present, search for updates, install them with an exclusion list, and reboot in a way the automation survives. Every prerequisite is its own step, so a failure tells you which one is missing instead of leaving you with a patch job that stopped for no visible reason.
powershell
# ===== Step 1 of 7: 01-CHECK-Internet.ps1 =====
# Check Internet Connection
#-----------------------------------------
$internet = (Test-Connection 8.8.8.8 -Count 1 -Quiet -ErrorAction SilentlyContinue)
if ($internet) {
Write-Host "<WRITE-LOG = ""*Internet connection: Available*"">"
}
else{
Write-Error "Internet connection: Unavailable"
}
# ===== Step 2 of 7: 02-CHECK-AdminRights.ps1 =====
# Ensure Administrator previlegues
#------------------------------------------------------
$isadm = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isadm) {
Write-Host "<WRITE-LOG = ""*Please run this script as Administrator.*"">"
Write-Host "<WRITE-LOG = ""*If this was a remote execution please provide Administrator credentials.*"">"
Write-Error "Warning: Not running as Administrator."
}
# ===== Step 3 of 7: 03-CHECK-PackageProvider.ps1 =====
# Check Packageprovider NuGet
#---------------------------------------
Write-Host "<WRITE-LOG = ""*Checking for NuGet package provider...*"">"
# Install NuGet provider if not present
if (!(Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) {
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false -WhatIf:$false -ErrorAction SilentlyContinue
Write-Host "<WRITE-LOG = ""*NuGet package provider installed successfully*"">"
} else {
Write-Host "<WRITE-LOG = ""*NuGet package provider already available*"">"
}
# ===== Step 4 of 7: 04-CHECK-UpdateModule.ps1 =====
# Install/Update WU Moduel
#-----------------------------------------
Write-Host "<WRITE-LOG = ""*Checking PSWindowsUpdate module...*"">"
# Install PSWindowsUpdate module
$module = Get-Module -Name PSWindowsUpdate -ListAvailable
if (!$module) {
Install-Module -Name PSWindowsUpdate -Force -AllowClobber -Confirm:$false -SkipPublisherCheck
Write-Host "<WRITE-LOG = ""*PSWindowsUpdate module installed successfully*"">"
} else {
Write-Host "<WRITE-LOG = ""*PSWindowsUpdate module already installed*"">"
}
# ===== Step 5 of 7: 05-CHECK-WindowsUpdates.ps1 =====
# Check Windows Updates
#----------------------------------------------------------
# Import the module
Import-Module PSWindowsUpdate -Force
Write-Host "<WRITE-LOG = ""*PSWindowsUpdate module imported*"">"
Write-Host "<WRITE-LOG = ""*Searching for Windows Updates...*"">"
# Get available updates with error handling
try {
$updates = Get-WindowsUpdate -MicrosoftUpdate -ErrorAction Stop
$count = $updates.Count
Write-Host "<WRITE-LOG = ""*Found $count Updates...*"">"
$store = $updates
} catch {
Write-Host "<WRITE-LOG = ""*ERROR: Failed to search for updates: $($_.Exception.Message)*"">"
Write-Error "ERROR: Failed to search for updates: $($_.Exception.Message)"
}
# ===== Step 6 of 7: 06-INSTALL-WindowsUpdates.ps1 =====
# Install Windows Updates, exclude KBs if needed
#------------------------------------------------------------
$updates = $store
Write-Host "<WRITE-LOG = ""*Load Store: $store*"">"
# Search Windows Updates again if needed
$updates = Get-WindowsUpdate -MicrosoftUpdate -ErrorAction Stop
if ($updates.Count -gt 0) {
Write-Host "<WRITE-LOG = ""*Found $($updates.Count) update(s) to install*"">"
# Display update details
$updates | ForEach-Object {
Write-Host "<WRITE-LOG = ""*Update: $($_.Title) (KB$($_.KB))*"">"
}
Write-Host "<WRITE-LOG = ""*Installing Windows Updates...*"">"
$scriptBlock = {
try {
Import-Module PSWindowsUpdate -Force
# Hide problematic updates (expand this list as needed)
$problematicKBs = @()
<# $problematicKBs = @('KB5034439', 'KB5034441', 'KB5005463', 'KB4535680', 'KB5008876') #>
foreach ($kb in $problematicKBs) {
try {
$update = Get-WindowsUpdate -KBArticleID $kb -ErrorAction SilentlyContinue
if ($update) {
Hide-WindowsUpdate -KBArticleID $kb -Confirm:$false
Write-Output "Hidden problematic update: KB$kb"
}
} catch {
Write-Warning "Could not hide KB$kb : $($_.Exception.Message)"
}
}
# Get and install updates
$updatesToInstall = Get-WindowsUpdate -MicrosoftUpdate -Install -AcceptAll -AutoReboot:$false -IgnoreReboot
# Stop any running PSWindowsUpdate scheduled tasks
$runningTasks = Get-ScheduledTask | Where-Object { $_.TaskName -like '*PSWindowsUpdate*' -and $_.State -eq 'Running' }
foreach ($task in $runningTasks) {
try {
Stop-ScheduledTask -TaskName $task.TaskName -Confirm:$false
} catch {
Write-Warning "Could not stop task $($task.TaskName): $($_.Exception.Message)"
}
}
} catch {
Write-Error "Update installation failed: $($_.Exception.Message)"
}
} #end scriptBlock
# Execute the update job
try {
$job = Invoke-WuJob -ComputerName $env:COMPUTERNAME -Script $scriptBlock -RunNow -Confirm:$false -ErrorAction Stop
Write-Host "<WRITE-LOG = ""*Update job started successfully*"">"
} catch {
Write-Host "<WRITE-LOG = ""*ERROR: Failed to start update job: $($_.Exception.Message)*"">"
Write-Error "ERROR: Failed to start update job: $($_.Exception.Message)"
}
# Monitor update progress
Write-Host "<WRITE-LOG = ""*Monitoring update progress...*"">"
$timeout = 3600 # 60 minute timeout
$timer = 0
$checkInterval = 30 # seconds
while ($timer -lt $timeout) {
$runningTasks = Get-ScheduledTask | Where-Object {
$_.TaskName -like '*PSWindowsUpdate*' -and $_.State -eq 'Running'
}
if ($runningTasks.Count -eq 0) {
Write-Host "<WRITE-LOG = ""*Windows Updates installation completed!*"">"
# Check if reboot is required
$rebootRequired = Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired"
if ($rebootRequired) {
Write-Host "<WRITE-LOG = ""*System restart required*"">"
} else {
Write-Host "<WRITE-LOG = ""*No restart required*"">"
}
break
}
# Progress update every 60 seconds
if ($timer % 60 -eq 0) {
Write-Host "<WRITE-LOG = ""*Updates in progress... ($([math]::Round($timer/60)) minutes elapsed)*"">"
}
Start-Sleep -Seconds $checkInterval
$timer += $checkInterval
}
if ($timer -ge $timeout) {
Write-Host "<WRITE-LOG = ""*WARNING: Update process timed out after $timeout seconds*"">"
}
} else {
Write-Host "<WRITE-LOG = ""*System is up to date.*"">"
}
# ===== Step 7 of 7: 07-SE-ManagedReboot.ps1 =====
# ServerEngine can handle system reboot cycles
# and seemlesy continue the next scripts when using Runbooks.
# ServerEngine initiates a waiting sequence and after
# successfull reconnection the cycle ends
#-------------------------------------------------
# Use this specific line for managed reboot cycles
Invoke-Command { Start-Sleep 3;Write-Host " ""State : Reboot"" ";Restart-Computer -Force }; Exit-PSSessionRun it across your fleet
This script runs as-is on a single host. Paste it into ServerEngine to schedule it, run it on a whole server group in parallel, and keep the credentials out of the file — see the scripts documentation and the credential store.
More in Runbooks
Ready when you are.
Try ServerEngine free for 7 days.