Worklet: Only Reboot Windows During Certain Time Frame

If you have Windows systems that you can’t afford to have rebooting outside of a certain time-frame, you can use this to restrict when a system that needs a reboot is allowed to reboot.

Customer’s scenario: “Outages of more than 1 minute are not acceptable. Because of the possibility patching itself running long (100+ servers patching at the same time in the same tower) we cannot tie a reboot to the policy doing the patching and would like to start patching before the maintenance window to mitigate that and force a reboot at the time of the maintenance window with a reboot worklet. There is a risk that patching will not only go really long but it could go past the entire maintenance window. With a check of system time in the remediation, if patching ran exceptionally long and the worklet queued up and ran after the scheduled time, the worklet would quit out with no reboot and we could reboot manually with approval from our customers.”

The systems will obviously need to not have any policies attached to them with auto-reboot turned on. This worklet will evaluate which systems need a reboot, and if they do, will reboot them if:

  1. The time the worklet is run against the system is within the defined time frame
  2. There has been no user input on the system for at least the defined amount of time

Thanks @jesumyip for the framework

Evaluation:

$sysInfo = New-Object -ComObject "Microsoft.Update.SystemInfo"
if($sysInfo.RebootRequired)
    { 
    exit 1}
else
    {
    exit 0}

Remediation:

# These parameters control the time of day window within which automatic reboot will be initiated (ie. 11am, 2:30pm)
[string]$rebootStartTime = "11pm"
[string]$rebootEndTime = "11:15pm"

# If there is no user input detected for $minIdleTime minutes within $rebootStarTime and $rebootEndTime, automatic reboot
# will be initiated. Set to 0 if for servers or not concerned about user input detected.
[int]$minIdleTime = 0

Add-Type @'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PInvoke.Win32 {

    public static class UserInput {

        [DllImport("user32.dll", SetLastError=false)]
        private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

        [StructLayout(LayoutKind.Sequential)]
        private struct LASTINPUTINFO {
            public uint cbSize;
            public int dwTime;
        }

        public static DateTime LastInput {
            get {
                DateTime bootTime = DateTime.UtcNow.AddMilliseconds(-Environment.TickCount);
                DateTime lastInput = bootTime.AddMilliseconds(LastInputTicks);
                return lastInput;
            }
        }

        public static TimeSpan IdleTime {
            get {
                return DateTime.UtcNow.Subtract(LastInput);
            }
        }

        public static int LastInputTicks {
            get {
                LASTINPUTINFO lii = new LASTINPUTINFO();
                lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
                GetLastInputInfo(ref lii);
                return lii.dwTime;
            }
        }
    }
}
'@


<# 

Two conditions must be met for automatic reboot
1. The local time must be between $rebootStartTime and $rebootEndTime
2. There has not been any user input for at least $idleTime minutes

#>

$boolTimeConditionMet = $False
$minTime = Get-Date $rebootStartTime
$maxTime = Get-Date $rebootEndTime
$now = Get-Date
if ($minTime.TimeOfDay -le $now.TimeOfDay -and $maxTime.TimeOfDay -ge $now.TimeOfDay) 
{
    $boolTimeConditionMet = $True
}

$boolIdleConditionMet = $False
$idleTime = [PInvoke.Win32.UserInput]::IdleTime
if ($idleTime.Minutes -ge $minIdleTime)
{
    $boolIdleConditionMet = $True
}

# Check if a reboot is pending
$sysInfo = New-Object -ComObject "Microsoft.Update.SystemInfo"
if($sysInfo.RebootRequired)
{
  if ($boolIdleConditionMet -and $boolTimeConditionMet)
  {
    Restart-Computer -Force
  }
}
1 Like