Custom Reboot Notifications

  • 14 September 2020
  • 5 replies
  • 382 views

Userlevel 4
Badge

From Worklet: Predictable Reboot Notifications for Windows


RebootNotifications.pdf (133.8 KB)


5 replies

Any chance you can post screenshots of what the popup prompts look like?

Userlevel 4
Badge

image

any chance we could see the eval/rem code in a non-PDF format?

Userlevel 4
Badge

Sure. Sorry I didn’t post earlier. I have been very busy working with OAuth 2. 🙂


Hidden.vbs

CreateObject("Wscript.Shell").Run """" & WScript.Arguments(0) & """", 0, False


Runtoasthidden.cmd

powershell.exe -executionpolicy Unrestricted -file "C:\ProgramData\amagent\rebootNotification\RebootToastNotification.ps1"


Runreboothidden.cmd

powershell.exe -executionpolicy Unrestricted -file "C:\ProgramData\amagent\rebootNotification\RebootWithoutNotification.ps1"


Rebootwithoutnotification.ps1

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;
}
}
}
}
'@

Function IsAutomaticReboot
{
<#

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
}

return ($boolIdleConditionMet -and $boolTimeConditionMet)
}

Function RebootComputer
{
Restart-Computer -Force
}

#---------------------------------------------------------------------------------------
# These parameters control the time of day window within which automatic reboot will be initiated
[string]$rebootStartTime = "1am"
[string]$rebootEndTime = "5am"

# If there is no user input detected for $minIdleTime minutes within $rebootStarTime and $rebootEndTime, automatic reboot
# will be initiated
[int]$minIdleTime = 30

# Check if a reboot is pending
$sysInfo = New-Object -ComObject "Microsoft.Update.SystemInfo"
if($sysInfo.RebootRequired)
{
if (IsAutomaticReboot)
{
RebootComputer
}
}

Reboottoastnotification.ps1

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;
}
}
}
}
'@

Function SaveToastLogo
{
param([string]$strImageFile)

$Base64Image = "......replace with your own image....."
if (!(Test-Path $strImageFile))
{
# Create an image file from base64 string and save to user temp location
[byte[]]$Bytes = [convert]::FromBase64String($Base64Image)
[System.IO.File]::WriteAllBytes($strImageFile,$Bytes)
}
}

Function ClearToastNotification
{
param([string]$toastAppID)
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotificationManager]::History.Clear($appID)
}

Function ShowToastNotification
{
param([string]$rebootStartTime, [string]$rebootEndTime)

[xml]$Toast = @"
<toast scenario="reminder">
<visual>
<binding template="ToastGeneric">
<text>BW IT Notice</text>
<text>Your computer was recently patched. Please restart manually, or automatic restart will occur between $rebootStartTime and $rebootEndTime.</text>
<image placement="appLogoOverride" hint-crop="circle" src="$ImageFile"/>
<group>
<subgroup>
<text hint-style="captionSubtle">If you need assistance, contact Help Desk.</text>
</subgroup>
</group>
</binding>
</visual>
<actions>
<action activationType="system"
arguments="dismiss"
content="Dismiss"
/>
</actions>
<audio src="ms-winsoundevent:Notification.Looping.Alarm3"/>
</toast>
"@

$Load = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$Load = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]

# Load the notification into the required format
$ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument
$ToastXml.LoadXml($Toast.OuterXml)

[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appID).Show($ToastXml)
}

Function IsAutomaticReboot
{
<#

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
}

return ($boolIdleConditionMet -and $boolTimeConditionMet)
}

Function RebootComputer
{
Restart-Computer -Force
}

Function IsTimeForReminder
{
param([int]$intMinutesBetweenReminders, [string]$registryPath)

$doReminder = $False
$intLastReminder = GetMinutesSinceLastReminder($registryPath)
if ($intLastReminder -ge $intMinutesBetweenReminders)
{
$doReminder = $True
UpdateReminderTracker($registryPath)
}
return $doReminder
}

Function GetMinutesSinceLastReminder
{
param([string]$registryPath)

$minutesSinceLastReminder = 999999
$boolReturnVal = VerifyReminderTracker($registryPath)
if ($boolReturnVal -eq $False)
{
$lastReminder = (Get-ItemProperty -Path $registryPath -Name "LastNotified").LastNotified
$lastReminder = [datetime]$lastReminder

$tempDate = New-TimeSpan -Start $lastReminder -End (Get-Date)
$minutesSinceLastReminder = $tempDate.TotalMinutes
}
return ($minutesSinceLastReminder)
}

Function UpdateReminderTracker
{
param([string]$registryPath)

VerifyReminderTracker($registryPath)
$now = [string](Get-Date)
Set-ItemProperty -Path $registryPath -Name "LastNotified" -Value $now | Out-Null
}

Function VerifyReminderTracker
{
param([string]$registryPath)

$boolFirstTimeRunning = $False
InitializeReminderTracker($registryPath)
$lastReminder = (Get-ItemProperty -Path $registryPath -Name "LastNotified").LastNotified
if (!([string]$lastReminder -as [DateTime]))
{
$now = [string](Get-Date)
Set-ItemProperty -Path $registryPath -Name "LastNotified" -Value $now | Out-Null
$boolFirstTimeRunning = $True
}
return $boolFirstTimeRunning
}

#--- Never call this function directly
Function InitializeReminderTracker
{
param([string]$registryPath)

$Now = (Get-Date)
$Now = $Now.AddMonths(-3)
$Now = [string]$now
if (!(Test-Path $registryPath))
{
New-Item -Path $registryPath -Force | Out-Null
New-ItemProperty -Path $registryPath -Name "LastNotified" -Value $Now -PropertyType STRING -Force | Out-Null
}

try
{
$lastReminder = (Get-ItemProperty -Path $registryPath -Name "LastNotified" -ErrorAction Stop).LastNotified
}
catch
{
New-ItemProperty -Path $registryPath -Name "LastNotified" -Value $Now -PropertyType STRING -Force | Out-Null
}
}

#---------------------------------------------------------------------------------------
[string]$appID = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe"

# Note: These next two variables work because this script is run as a scheduled task configured to execute as the currently logged on user.
# This is needed so that user is able to see the toast notification. If the task runs under the SYSTEM context, the toast notification
# will not show.
[string]$ImageFile = "$env:TEMP\BWToastLogo.png"
[string]$NotificationRegistryPath = "HKCU:\Software\BW\RebootNotification"

# These parameters control the time of day window within which automatic reboot will be initiated
[string]$rebootStartTime = "1am"
[string]$rebootEndTime = "5am"

# If there is no user input detected for $minIdleTime minutes within $rebootStarTime and $rebootEndTime, automatic reboot
# will be initiated
[int]$minIdleTime = 30

# How many times per day to show reboot reminder toast notification
[int]$reminderTimesPerDay = 1

# Dump the base-64 image into a file on disk. This image is used in the toast notification.
# If the image is missing, the toast notification will not show.
SaveToastLogo($ImageFile)

# Clear any previous toast notifications generated by Powershell. This will ensure we don't have notifications that are stacked (they show one at a time
# immediately after you dismiss one). This may confuse / irritate a user.
ClearToastNotification($appID)

# Check registry key exists. Create if it doesn't exist.
VerifyReminderTracker($NotificationRegistryPath)
[int]$minutesBetweenReminders = [int]((24 / $reminderTimesPerDay) * 60)

# Check if a reboot is pending
$sysInfo = New-Object -ComObject "Microsoft.Update.SystemInfo"
if($sysInfo.RebootRequired)
{
if (IsAutomaticReboot)
{
RebootComputer
}
else
{
if (IsTimeForReminder $minutesBetweenReminders $NotificationRegistryPath)
{
ShowToastNotification $rebootStartTime $rebootEndTime
}
}
}

you da man!

Reply