Automating Windows Updates with PowerShell and Octopus

Add me on Twitter, Bluesky or LinkedIn! I'm rebuilding my network from the ground up. You can cite this post as a reason for reaching out if you're shy.

It’s a quiet Saturday morning here in lovely Tampere, and I’m watching the update progress on an ancient bit of fintech from Windows COM programming days. It’s as simple as it gets, operationally speaking. There’s a handful of Windows server VMs; a few SQL Server machines, a few more for the business logic, and a few for the frontend. An SFTP server for flavor - don’t sleep on SFTP just because REST APIs are all the rage nowadays.

‘Bag of VMs’ of course means ‘mandatory OS patching’, which is what I’m up to today. One of Microsoft’s many sins is not including a command line OS patcher, so schlubs like me have to resort to a third party PowerShell package called PSWindowsUpdate to automate the job.

Getting this baddie on these Internet-shy machines Internet is a story for another day. Once I did get it on, I got to use a tool which is much better than I was expecting it to be: Octopus Deploy, a system orignally designed for CI/CD stuff but which happens to serve admirably as a remote script executor in a pinch. Since I inherited this architecture with Octopus already in place anyway, I just stuck with this instead of installing something sane like Ansible.

The setup is simple:

  1. You install Octopus on some oct-vm.
  2. You install Octopus tentacles on all machines you wish to deploy to (or just run scripts on).
  3. Unlike Ansible, you get to use a quite nice and readable web front-end to customize the steps you want your scripts to run in, which machines you want them to run on, whether it should be run on all machines at once or one at a time round-robin style, …
  4. As your scripts run remotely, any Write-Debugs you included get streamed to the front-end as well, letting you watch in real time as your scripts whir elsewhere.

There’s a lot of flexibility in the Octopus web frontend. And what I’m a particular fan of is just how discoverable all of this flexibility feels – that’s something you don’t get out of the box with ordinary CLIs, even if their composability is exactly what I’d reach for in past a certain bar of complexity. (I have a coworker who is a big fan of this new wave of halfway-house TUIs that is mostly getting baked up by the Rust community, which shows some promise towards bringing GUI-level composability direct to the terminal. I’m not fully sold yet - but I do have to admit fzf’s minimalistic TUI has fast become a mainstay in my workflows. So maybe there’s a there there, I don’t know.)


I later realized PSWindowsUpdate was probably hanging due to all of the logged-in but disconnected users. So I added a step using quser, msg, and some awk-flavored splitting to kick everyone off the server with a short 5 minute leadup. This SO answer contained a script which I used almost unaltered, save for a

msg $session.Username 'Updates are about to begin. You will
be kicked in 60 seconds.'
Start-Sleep 60

above the logoff.