Update: 201006018 – changed the reboot portion of the script to watch uptime instead of ping, some of my servers weren’t coming down fast enough. B)
For our last patch cycle we used Harry Johnston's vbs script to manage our updates. His script is here. (His very basic home page is here. I found the script from a newsgroup post or I would ping back or similar. Thanks Harry if you see this.)
If you want to do this completely with powershell, James O’Neill has posted is a pretty definitive discussion, Managing Windows Update with PowerShell. I don’t have powershell on all of my servers and don’t want to make that a requirement for this solution. As such, we are doing the actual work w/ the already present cscript engine and just managing the process w/ powershell.
Harry’s very useful script works whether you are pointing to windows updates or your own WSUS server. It checks for new updates, downloads, installs and reports whether you need a reboot. If you don't need a reboot, it runs itself again until it either needs a reboot or finds no patches. Cool script. we modified it slightly to spit out the hostname we were working w/ as we psexec'd it to run on a bunch of computers.
When we did this at first, we ran a few loops. First we copied the winupdates.vbs script to the c:\ on each computer to be updated. We would have run it from the fileshare but we wanted to have a single solution for our servers. Win2k8 servers run into a UAC problem w/ psexec. there may be a better way but we decided to pass -s to psexec to run as system (thus avoiding the UAC issues). Because we need to run as system, we cannot call the script from a network location (more specifically, we didn’t want to manage opening a network location that could be read by all the machine accounts)
We then ran a loop to pop out a command window for each server and run the script through sysinternal's psexec. When the script finished, it would report if it needed a boot. We would boot and then run the script again until the script had nothing to do.
This was much easier than getting on the servers and doing it but meh, it was still too much work.
The below powershell script will automate the copying the script and then loop the running, booting, waiting until there are no more patches to install. I did put in a 10 boot limit and a 15 minute limit waiting for reboots.
Save the script below as PSPatch.ps1. you will need to update the path to your winupdates.vbs and psexec at the top. To run it, just pass it a server name.
./pspatch.ps1 servertopath
Let me know if you use it...
#patch server
param (
$server
)
$scriptpath = "c:\toolkit\scripts\winupdates.vbs"
$psexec = "C:\Toolkit\sysint\psexec.exe"
if (-not (Test-Path $scriptpath)) { throw "Could not find Winupdates.vbs. Set location at top of script." }
if (-not (Test-Path $psexec)) { throw "Could not find psexec. Set location at top of script." }
#make sure the name is resolvable
try {$dnsresult = [System.Net.DNS]::GetHostEntry($server)}
catch {
throw "Servername '$server' does not resolve to an IP"
}
#check if the server is alive
$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $ping.Send($server)
if ($($Reply.status) -ne "Success") {
throw "Could not ping $server. Response $($reply.Status). Skipping..."
}
#check if winupdate.vbs is in place
if (-not (test-path "\\$server\c$\winupdates.vbs")) {
Write-Host "Copying winupdates.vbs to \\$server\c$" -ForegroundColor Yellow
copy $scriptpath \\$server\c$
}
$i = 0 #as a sanity check we are only allowing 10 loops
while ($i -lt 10) {
$i++
#run update
&$psexec \\$server -s cscript c:\winupdates.vbs
#check exit code
switch ($LASTEXITCODE) {
2 { # no more updates to install
write-host "Servername '$server' is fully patched after $i loops" -ForegroundColor green
exit
}
3 { #reboot needed
Write-Host "Reboot required..." -ForegroundColor Yellow
$j = 0 #as a sanity check we are only allowing 15 loops here as well
$uptime = (Get-WmiObject -computer $server -class Win32_PerfFormattedData_PerfOS_System).SystemUptime
shutdown /r /m $server
while ($uptime -gt 600 ) { # We are checking the uptime and waiting until we get a value of less than 5 minutes uptime as our ‘reboot finished’ test
$j++
sleep 60
$ErrorActionPreference = "Stop"
try {
$uptime = (Get-WmiObject -computer $server -class Win32_PerfFormattedData_PerfOS_System).SystemUptime
Write-Host "Waiting for server to reboot ($uptime)"
}
catch {
Write-Host "Waiting for server to reboot ($uptime)"
}
if ($j -gt 15) { #if we have looped 15 times we have a problem
throw "Server has not rebooted in 15 minutes. Something is wrong"
}
}
}
default { #error or unexpected code
throw "We either have an error above or an unexpected error code. Please check above for details"
}
}
}