Super Compact, DevOps-ish Pending Reboot Test for The Rebootiest Operating System in The Cloud

Super Compact, DevOps-ish Pending Reboot Test for The Rebootiest Operating System in The Cloud

Windows and reboots - more than a few candles have been burned on both ends in understanding and resolving this relationship. Like it or not and despite Microsoft’s efforts - Windows is the most rebootiest operating system around.

There has to be a ton of code written around this - is it possible add a new contribution of real value?

I think it is - by being concise around my specific context of software deployment automation for DevOps in the cloud with a brutal eye to compactness.

Get-PendingReboot Heritage and A New Reboot Detection

Quite some time ago I created an earlier version of this code with some of the core reboot conditions being drawn from the long standing Get-PendingReboot solution by bcwwilhite. To optimize for my scenario, that code was severely compacted.

Recently I had to visit it again as the goal behind the automation I was writing was to detect that a reboot is pending due to removing Windows Defender while installing an alternate solution. I found that Get-PendingReboot does not detect this type of pending reboot. The code included here is updated to be able to detect that reboot by the contents of a file. (Note that it can also be detected by doing an uninstall-windowsfeature on a feature you know has already been removed and the return value will show you whether a previously triggered reboot is pending - but that is hard to write as a general solution that will never do something nasty.)

What do I mean “DevOps Deployment Automation”?

When I engineer DevOps solutions I have a strong orientation to creating bits of code that are compact enough to reside within the script they are used in. In fact, the core goal is frequently a single script. This allows flexibility like easily running directly from a source code repository and easy distribution as inline code for orchestration systems that support it because the need to push a file can add a whole new level of complexity to the automation.

For the most part DevOps Software Deployment Automation runs directly on the node that is being automated - so part of the compaction was to eliminate Get-PendingReboot’s ability to execute remotely. As a general solution, remote operation is a fantastic feature - but it adds a substantial amount of code.

Additionally, Get-PendingReboot contained significant code to handle checking for SCCM reboots and while this is also a great feature for a general solution, I have not seen SCCM used for software deployment in a DevOps toolchain - so it was removed.

Compacting Code: Smallness and Embedding Over Style and Readability

Compacting code tacks strongly toward size over typical style and readability guidelines. I do this because:

  1. the code will be included so frequently in so many things and
  2. due to it’s simplicity, updates should be rare.

Compacting and including also avoids shared code hell by testing and isolating it to each solution that uses the template code. If the code is embedded (a form of isolation) and worked under testing of a given automation solution, then updates to the template code will not break existing automation because it will never be used by previous solutions that leveraged it. Even if the code is not embedded, it should be isolated. If the code is in a script file, it should be dot source or if a module, loaded by file name from the local location - with this isolation methodology you would avoid installing modules into any shared module folder on the target machine.

By not sharing code across automation solutions, the freedom and flexibility from backward compatibility engineering and regression testing is truly liberating.

Keep in mind I build developer automation tooling in a SaaS company that has 10’s of thousands of instances, thousands of developers and hundreds of products. I do not have the luxury of assuming or managing all instances to a standard set of runtimes. Nor do I have the luxury of knowing what additional tooling has taken a dependency on my code.

However, I have taken the same “favor isolation” approach in environments where I did have the ability to manage it more closely and there were still huge benefits to favoring code isolation.

The Code

The below Test-PendingReboot code is the compacted function. If you want it to be more compact, you can remove the comments. A sample usage is also given.

Function Test-PendingReboot
{
  Return ([bool]((get-itemproperty "hklm:SYSTEM\CurrentControlSet\Control\Session Manager").RebootPending) -OR 
  [bool]((get-itemproperty "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update").RebootRequired) -OR 
  [bool]((get-itemproperty "HKLM:SYSTEM\CurrentControlSet\Control\Session Manager").PendingFileRenameOperations) -OR 
  #Computer Rename pending
  ((get-itemproperty 'HKLM:SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\' | Select -Expand 'ComputerName') -ine (get-itemproperty 'HKLM:SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\' | Select -Expand 'ComputerName')) -OR 
  #Domain Join Pending
  ((Test-Path "HKLM:SYSTEM\CurrentControlSet\Services\Netlogon\JoinDomain") -OR (Test-Path "HKLM:SYSTEM\CurrentControlSet\Services\Netlogon\AvoidSpnSet"))) -OR
  #WindowsFeature install or uninstall has a pending reboot
  ((test-path c:\windows\winsxs\pending.xml) -AND ([bool](get-content c:\windows\winsxs\pending.xml | Select-String 'postAction="reboot"')))
}

# Sample Usage
If (Test-PendingReboot)
{ Write-Host "Shutting down in 10 seconds (giving time for orchestrating automation to close out)..."
  shutdown.exe /r /t 10 }
Else {Write-Host "A reboot is not pending, no action taken"}

Code For This Article

CompactDevOpsRebootWindowsIfNeeded.ps1