Schedule Windows Notifications with PowerShell (Windows Task Scheduler / Toast Notifications)

I’ve found too many times that I can sit in front of a computer screen for several hours without taking a break but that later in the day the eye strain catches up with me. I wanted something to remind me to take a quick break every 20 minutes, following the 20-20-20 rule. There’s a lot of programs that do something like this. Workrave may be the most popular, but I’ve had a few problems with it and decided I could probably do something simpler with a scheduled PowerShell script.

Notifications

First off, I need the notification. There’s a handful of blogs on how to make a toast notification in PowerShell. There’s a PowerShell module for it, but I’d rather not have to install an external module. From the creator of that module, here’s a working example of creating a toast notification in PowerShell:


$app = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$Template = [Windows.UI.Notifications.ToastTemplateType]::ToastImageAndText01
#Gets the Template XML so we can manipulate the values
[xml]$ToastTemplate = ([Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent($Template).GetXml())
[xml]$ToastTemplate = @"
<toast launch="app-defined-string">
<visual>
<binding template="ToastGeneric">
<text>DNS Alert…</text>
<text>We noticed that you are near Wasaki. Thomas left a 5 star rating after his last visit, do you want to try it?</text>
</binding>
</visual>
<actions>
<action activationType="background" content="Remind me later" arguments="later"/>
</actions>
</toast>
"@
$ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument
$ToastXml.LoadXml($ToastTemplate.OuterXml)
$notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($app)
$notify.Show($ToastXml)

I’m just going to copy what I need and modify and condense it. For more reading on toast notifications in PowerShell this blog article and this one provide more detail, but be sure to note that since they were published, it’s become necessary to use a real app id to get the notification to show – details here.

Here’s the selection of code I’m using for the notification.

Reminder.ps1:

$app = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]

$Template = [Windows.UI.Notifications.ToastTemplateType]::ToastImageAndText01

[xml]$ToastTemplate = ([Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent($Template).GetXml())

[xml]$ToastTemplate = @"
<toast launch="app-defined-string">
  <visual>
    <binding template="ToastGeneric">
      <text>Prevent Eye Strain</text>
      <text>Take a 20 second break</text>
    </binding>
  </visual>
</toast>
"@

$ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument
$ToastXml.LoadXml($ToastTemplate.OuterXml)

$notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($app)

$notify.Show($ToastXml)

#source: https://gist.github.com/Windos/9aa6a684ac583e0d38a8fa68196bc2dc

Running this script will create the following notification:

VBS Launcher

The one problem with this is that when run through the task scheduler, a PowerShell console window will flash briefly before the notification displays. To avoid this intrusive popup, a short VBScript program can be used to launch the PowerShell script we’ve just created.

Launcher.vbs:

Dim shell,command
command = "powershell.exe -nologo -command ""C:\Scripts\Reminder.ps1"""
Set shell = CreateObject("WScript.Shell")
shell.Run command,0

 

Scheduling

The next step is to schedule this script to run and produce the notification every 20 minutes. This ended up being way more complex than I originally imagined. The parameters for New-ScheduledTaskTrigger don’t allow setting a RepetitionInterval with a Daily or AtLogOn parameter, which makes doing this a little tricky. In the end, I took some inspiration from this solution. The best solution simply seemed to be to set up the task I want in the Windows Task Scheduler GUI, export the XML, and then use that in the PowerShell script. At this point I’m not really doing much in PowerShell itself to create the task, however, ultimately this still creates a code-based solution that can easily be put into other scripts or run on a server without a GUI.

After creating the task in Windows Task Scheduler, I ran

Export-ScheduledTask "BreakReminder"

in PowerShell and get the following XML:

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Description>A reminder every 20 minutes to take a 20 second break to prevent eye strain.</Description>
    <URI>\BreakReminder</URI>
  </RegistrationInfo>
  <Settings>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
  </Settings>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2019-06-10T00:00:00</StartBoundary>
      <Repetition>
        <Interval>PT20M</Interval>
        <Duration>P1D</Duration>
        <StopAtDurationEnd>true</StopAtDurationEnd>
      </Repetition>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Scripts\Launcher.vbs</Command>
    </Exec>
  </Actions>
</Task>

I then squeezed that all into one line and end up with the following very messy lines of PowerShell code that can be used to create the task from scratch.  The Reminder.ps1 and Launcher.vbs files from above just need to be saved somewhere and the file paths in Launcher.vbs and the below code need to be updated accordingly.

$TaskXML = '<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo> <Description>A reminder every 20 minutes to take a 20 second break to prevent eye strain.</Description> <URI>\BreakReminder</URI> </RegistrationInfo> <Triggers> <CalendarTrigger> <Repetition> <Interval>PT20M</Interval> <Duration>P1D</Duration> <StopAtDurationEnd>true</StopAtDurationEnd> </Repetition> <StartBoundary>2019-06-10T00:00:00</StartBoundary> <Enabled>true</Enabled> <ScheduleByDay> <DaysInterval>1</DaysInterval> </ScheduleByDay> </CalendarTrigger> </Triggers> <Settings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>false</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <AllowStartOnDemand>true</AllowStartOnDemand> <Enabled>true</Enabled> <Hidden>false</Hidden> <RunOnlyIfIdle>false</RunOnlyIfIdle> <WakeToRun>false</WakeToRun> <ExecutionTimeLimit>P3D</ExecutionTimeLimit> <Priority>7</Priority> </Settings> <Actions Context="Author"> <Exec> <Command>C:\Scripts\Launcher.vbs</Command> </Exec> </Actions> </Task>'

Register-ScheduledTask "BreakReminder" -Xml $TaskXML.OuterXml