Powershell Remote Windows Updates

Current employer has 3 forests, 3 domains, and 2 WSUS servers. During Covid (operationally speaking anyway), we’re in a 95% work-from-home status. One of our WSUS servers at some point in time decided to fill up its disks with updates. At another point in time, no one on the team thought it would be a good idea to setup monitoring or alerting for this system. Yay. Long story short, I have a love/hate relationship with WSUS.

For on-prem systems it works fairly well. GPOs put systems into specific groups (Workstations, Servers, Pilot Groups, etc), and an-eventually-implemented naming convention will allow IT Personnel to easily identify DEV, ADM, and PRD systems at a glance. Approving updates, pushing updates, and reporting updates all works.

What doesn’t work, however, is the automatic installation of updates. This post will turn into 2 posts: 1) Server-related, and 2) Workstation-related. The workstations, especially those that are remote, aren’t patching themselves on the regular. Probably because the users don’t VPN in often (or long enough) AND whomever set WSUS rules up didn’t specify an install-by required date for updates. Users are lazy and don’t like to reboot (myself included), so this just compounds the issue.

However, this post was more for the Server updates path. WSUS was setup to download patches, but the GPO for servers indicates that at no time will the patches be installed on servers. The previous regime had used batchpatch for that purpose. I’ve used PDQdeploy – SSDD. But since I said “previous regime”, and the last member of that batchpatch crowd left over a year ago, we’ve been woefully underpatched since pre-Covid.

Enter PowerShell. Note: I may clean this up a bit, but for now it’s the messy workthrough.

On EVERY managed system you need to have the following pre-requisites:

  • An Administrator account
  • WSMan configured to allow the host(s)
  • WinRM configured
  • PSWindowsUpdate PS Module

  1. Open PowerShell as an Administrator
  2. winrm /quickconfig
  3. Set-Item WSMAN:\localhost\client\trustedhosts -Value *
  4. Install-Module -Name PSWindowsUpdate

If the PSWindowsUpdate fails to install it’s generally due to the fact that you’re running PS 5.2 or below and it’s failing TLS requirements to run NuGet. Enable strongencryption on powershell, restart powershell, and re-run the PSWindowsUpdate to continue.

  • Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
  • Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord

Now we want to create the PS1 file. I located this in my C:\scripts directory on a domain controller because it’s easier that way.

$Server = Read-Host -Prompt 'Enter a Fully Qualified computername.domain.tld - or multiple computers separated by comma and space'
$Credential = Get-Credential
Invoke-WUJob -ComputerName $Server -Credential $Credential -Script {Import-Module PSWindowsUpdate; Install-WindowsUpdate -NotCategory 'Drivers' -MicrosoftUpdate -AcceptAll -IgnoreReboot -SendReport -PSWUSettings @{SmtpServer='YOURSMTPSERVER.DOMAIN.TLD';From='WSUS@YOURDOMAIN.TLD';To='ITUSER@YOURDOMAIN.TLD';Port=25} | Out-File C:\PSWindowsUpdateLog.txt -Append} -Confirm:$false -verbose -RunNow

I’ll eventually remove the credential ask (and hardcode one in) and also have it pull from a comma delaminated file listing all of the required servers. Perhaps I’ll have it ask “which domain” with selections 1 to 3, then “enter credentials for XYZ domain”.

From here we want to save that PS1 file and then run it from the domain controller. Right-click run with powershell. Follow along. I should note that I have the following auto-admin code at the top of the script:

Check for run as administrator
 param([switch]$Elevated)
 function Test-Admin {
     $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
     $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
 }
 if ((Test-Admin) -eq $false)  {
     if ($elevated) {
         # tried to elevate, did not work, aborting
     } else {
         Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition))
     }
     exit
 }

Look for updates to this post for the workstations or servers. Or not. I get lazy sometimes.

Leave a Reply

Your email address will not be published. Required fields are marked *