You may have seen that many apps these days use “Toast Notifications” to inform the user of an event or to ask them to do something. If you have moved your workload to Intune for Windows Update Policies you would have encountered them for sure. UWP and Desktop Apps can leverage the ToastNotification and ToastNotificationManager Class from the Windows.UI.Notifications Namespace to create ToastNotifier objects to send a Toast Notification similar to below

Background
If you are reading this post, you will have seen some Toast Notifications and thought “Cool, can I do that?” and that is where my journey started. Rewind a whole bunch of months and we were discussing how we cold reduce our software portfolio to make management easier and bring down our running costs at the same time. One piece of software in our portfolio was used to send notifications to our user base to inform them of issues that affected the whole company e.g. Email Down / Phones Down / Coffee Machine Down
And so the challenge began. Could I create my own Toast Notifications, on the fly, and deliver them using ConfigMgr – and the answer is yes!
Feeling Toastie
Toast Notifications are flexible in their appearance and actionable states. Toasts can be delivered from a variety of predefined Toast Template types. They can range from simple text notifications to text with images.
Learn more about the different Toast Template Types at https://docs.microsoft.com/en-us/uwp/api/windows.ui.notifications.toasttemplatetype?view=winrt-19041#fields
The Toast itself is built and styled from from an XML. In the XML we specify the various visual elements and actionable elements within our Toast. Do we want images, titles, text, buttons etc. Here is an example of what a basic, text only, Toast XML might look like using the Toast Template Type ToastText02
1 2 3 4 5 6 7 8 |
<toast> <visual> <binding template="ToastText02"> <text id="1">My First Notification</text> <text id="2">I am so excited I sent you this</text> </binding> </visual> </toast> |

or we can get funky and include a Badge Image using Toast Template Type ToastImageAndText03 *
1 2 3 4 5 6 7 8 9 |
<toast> <visual> <binding template="ToastImageAndText03"> <text id="1">My First Notification</text> <text id="2">I am so excited I sent you this</text> <image id="1" src="C:\Scripts\badgeimage.jpg" /> </binding> </visual> </toast> |
* UPDATE: 25/10/20 – At this point I was asked to show the full code in order to display these simple toast templates. The full code is below but please continue reading the full article if you want an understanding of the code used
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#Specify Launcher App ID $LauncherID = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe" #Load Assemblies [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null #Build XML Template [xml]$ToastTemplate = @" <toast> <visual> <binding template="ToastImageAndText03"> <text id="1">My First Notification</text> <text id="2">I am so excited I sent you this Ben</text> <image id="1" src="C:\Scripts\badgeimage.jpg" /> </binding> </visual> </toast> "@ #Prepare XML $ToastXml = [Windows.Data.Xml.Dom.XmlDocument]::New() $ToastXml.LoadXml($ToastTemplate.OuterXml) #Prepare and Create Toast $ToastMessage = [Windows.UI.Notifications.ToastNotification]::New($ToastXML) [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($LauncherID).Show($ToastMessage) |

Why does the example above display the text Windows PowerShell? This is known as the Attribution property and is visible as we start to use more advanced Toast Template Types. The attribution property will be the name of the UWP or Desktop app that we use to call the Toast. For Windows Anniversary update and later, we can append to this value by modifying an element within our XML
1 2 3 4 5 6 7 8 9 10 |
<toast duration="$ToastDuration"> <visual> <binding template="ToastImageAndText03"> <text id="1">My First Notification</text> <text id="2">I am so excited I sent you this</text> <text placement="attribution">via your IT Team</text> <image id="1" src="C:\Scripts\badgeimage.jpg" /> </binding> </visual> </toast> |

So you can see we can add different properties into our XML to style the Toast the way we want it to look. In the PowerShell script this post, we are using another binding template called ToastGeneric. More information on how to style your Toasts can be found at https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-ux-guidance
Images
Images are interesting. We can have Badge Images (also know as App Logo Override Images), Hero Images and Inline Images. More information on all of these, including size and dimension restrictions can be found at https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#image-size-restrictions

In the Toaster
This post could very easily turn into a how to build a Toast tutorial so I will stop at this point. As I said at the beginning of the post, we had a very specific task we wanted our Toast to perform. The Toast, in our late example, will display a header image, known as a Hero Image, a Badge Image, some custom titles and text and two action buttons – one that opens the Service Portal Announcement page and the other that dismisses the Toast Notification. This will be used so the Service Desk can notify the user base of impending doom or “Systems Down”. I believe this requirement and style of Toast could very easily apply to other organisations. We wanted to be able to send our users notifications for organisation events that affect either everyone or groups of people. So maybe this is a good point to introduce a Toast limitation.
Toasts must be run in the USER context
It would have been neat to be able to target devices but Toasts will only pop if ran in the users context. This means our delivery mechanism must target users. This immediately rules out the way I would have liked to have pushed Toasts out – and that was via the “Push Script” feature in ConfigMgr. Sure, given a few hours/days in the engine room we could muster this in the script to deploy as SYSTEM – happy to collaborate if anyone has any ideas.
As we saw earlier, we have to specify which UWP or Win32 Desktop application we are going to use to “Pop the Toast” – man I love that term. I wanted to be able to “Pop a Toast” to our users, giving them some initial information and then have them access the ICT Service Portal to find more information and updates. If you are using a Desktop App, that App MUST exist in the Apps Start-Up folder in Windows. You will need the AppID of the App you choose to “Pop Your Toast”. In the following example, I chose MSEdge. I had originally tried two other very plausible Desktop Apps and these worked well too:-
- Microsoft.SoftwareCenter.DesktopToasts
- {1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe
Because I was launching a web page with an action button in my toast, using MSEdge as the app that handled my custom Toasts meant the browser would fire up in focus with the URL I specified in my Toast action argument.
You can find a list of available Apps and associated AppIDs by running the following command:-
1 |
Get-StartApps |

Note: Toasts can launch a browser when you specify Protocol on your action button as the activation type. If you want to run a custom action then you must register your own protocol to do this and that is outside of the scope of this blog post.
1 2 3 |
<actions> <action arguments="https://byteben.com" content="Click Me" activationType="protocol" /> </actions> |
Spreading Code on our Toast
Hopefully you understand some of the structure required to a “Pop a Toast” now. We have covered the XML requirements and how we want our Toast to look but we haven’t discussed how we deliver it to our users. We are wrapping our XML styling in a PowerShell script. As with all scripts I write, anything that “could” change I like to pass as a parameter. It is safe to assume that if you want to send a notification to users, you may want to change the wording at each catastrophe. I don’t really want the Service Desk guys and gals modifying scripts and updating content for the application. So we needed a way to read Toast text on the fly. In our example, we are doing this with another XML file. This file will be stored on a server accessible by everyone. The PowerShell script will read the XML elements in and set them as variables. Cool. We can have an XML that the service desk folks change each time or we could have multiple, pre fabricated XML’s for various scenarios. We created XML’s for the more common scenarios like “Email Down”, “Phones Down”, “Coffee Machine Down” etc.
Here is an example of what our XML will look like
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="UTF-8"?> <ToastContent> <ToastTitle>We want to bring to your attention some important information. Please review the details below before contacting the Service Desk</ToastTitle> <Signature>Sent on behalf of the ICT Service Desk</Signature> <EventTitle>Major IT Issues - Flooding</EventTitle> <EventText>We are currently experiencing problems with all our systems. We are drinking coffee with our feet up and will provide an update shortly. Thank you for your patience</EventText> <ButtonTitle>Details</ButtonTitle> <ButtonAction>https://byteben.com</ButtonAction> </ToastContent> |
Lets look at each of these elements briefly:-
- ToastTitle – The Title of our Toast
- Signature – The text beside the attribution field (only visible in Anniversary update or higher)
- EventTitle – Title of the Event we are bringing to our users attention
- EventText – Details of the event and instructions to the user
- ButtonTitle – Our Single, actionable button Title
- ButtonAction – Web page / Service Desk Portal to load
And here is where the elements (in red) appear on the Toast Notification:-

Script
Source https://github.com/byteben/Toast/blob/master/Toast_Notify.ps1
We are doing some basic checks like does the XML exist, is it a valid, readable XML, get the current user name (Domain Joined clients Only) for a more custom experience and load the assemblies to run the Toast etc. I will keep the ReadMe.md updated on GitHub.
Update
Version 2.0 – 07/02/2021
-Basic logging added
-Toast temp directory fixed to $ENV:\Temp\$ToastGUID
-Removed unnecessary User SID discovery as its no longer needed when running the Scheduled Task as “USERS”
-Complete re-write for obtaining Toast Displayname. Name obtained first for Domain User, then AzureAD User from the IdentityStore Logon Cache and finally whoami.exe
– Added “AllowStartIfOnBatteries” parameter to Scheduled Task
Version 1.2.105 – 05/002/2021
-Changed how we grab the Toast Welcome Name for the Logged on user by leveraging whoami.exe – Thanks Erik Nilsson @dakire
Version 1.2.28 – 28/01/2021
-For AzureAD Joined computers we now try and grab a name to display in the Toast by getting the owner of the process Explorer.exe
-Better error handling when Get-xx fails
Version 1.2.26 – 26/01/2021
-Changed the Scheduled Task to run as -GroupId “S-1-5-32-545” (USERS).
When Toast_Notify.ps1 is deployed as SYSTEM, the scheduled task will be created to run in the context of the Group “Users”.
This means the Toast will pop for the logged on user even if the username was unobtainable (During testing AzureAD Joined Computers did not populate (Win32_ComputerSystem).Username).
The Toast will also be staged in the $ENV:Windir “Temp\$($ToastGuid)” folder if the logged on user information could not be found.
Thanks @CodyMathis123 for the inspiration via https://github.com/CodyMathis123/CM-Ramblings/blob/master/New-PostTeamsMachineWideInstallScheduledTask.ps1
Version 1.2.14 – 14/01/21
-Fixed logic to return logged on DisplayName – Thanks @MMelkersen
-Changed the way we retrieve the SID for the current user variable $LoggedOnUserSID
-Added Event Title, Description and Source Path to the Scheduled Task that is created to pop the User Toast
-Fixed an issue where Snooze was not being passed from the Scheduled Task
-Fixed an issue with XMLSource full path not being returned correctly from Scheduled Task
Version 1.2.10 – 10/01/21
-Removed XMLOtherSource Parameter
-Cleaned up XML formatting which removed unnecessary duplication when the Snooze parameter was passed. Action ChildNodes are now appended to ToastTemplate XML.
Version 1.2 – 09/01/21
Added logic so if the script is deployed as SYSTEM it will create a scheduled task to run the script for the current logged on user
Version 1.1 – 30/12/20
Added Snooze Switch option
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
<# =========================================================================== Created on: 22/07/2020 11:04 Created by: Ben Whitmore Filename: Toast_Notify.ps1 =========================================================================== Version 2.0 - 07/02/2021 -Basic logging added -Toast temp directory fixed to $ENV:\Temp\$ToastGUID -Removed unnecessary User SID discovery as its no longer needed when running the Scheduled Task as "USERS" -Complete re-write for obtaining Toast Displayname. Name obtained first for Domain User, then AzureAD User from the IdentityStore Logon Cache and finally whoami.exe - Added "AllowStartIfOnBatteries" parameter to Scheduled Task Version 1.2.105 - 05/002/2021 -Changed how we grab the Toast Welcome Name for the Logged on user by leveraging whoami.exe - Thanks Erik Nilsson @dakire Version 1.2.28 - 28/01/2021 -For AzureAD Joined computers we now try and grab a name to display in the Toast by getting the owner of the process Explorer.exe -Better error handling when Get-xx fails Version 1.2.26 - 26/01/2021 -Changed the Scheduled Task to run as -GroupId "S-1-5-32-545" (USERS). When Toast_Notify.ps1 is deployed as SYSTEM, the scheduled task will be created to run in the context of the Group "Users". This means the Toast will pop for the logged on user even if the username was unobtainable (During testing AzureAD Joined Computers did not populate (Win32_ComputerSystem).Username). The Toast will also be staged in the $ENV:Windir "Temp\$($ToastGuid)" folder if the logged on user information could not be found. Thanks @CodyMathis123 for the inspiration via https://github.com/CodyMathis123/CM-Ramblings/blob/master/New-PostTeamsMachineWideInstallScheduledTask.ps1 Version 1.2.14 - 14/01/21 -Fixed logic to return logged on DisplayName - Thanks @MMelkersen -Changed the way we retrieve the SID for the current user variable $LoggedOnUserSID -Added Event Title, Description and Source Path to the Scheduled Task that is created to pop the User Toast -Fixed an issue where Snooze was not being passed from the Scheduled Task -Fixed an issue with XMLSource full path not being returned correctly from Scheduled Task Version 1.2.10 - 10/01/21 -Removed XMLOtherSource Parameter -Cleaned up XML formatting which removed unnecessary duplication when the Snooze parameter was passed. Action ChildNodes are now appended to ToastTemplate XML. Version 1.2 - 09/01/21 -Added logic so if the script is deployed as SYSTEM it will create a scheduled task to run the script for the current logged on user. -Special Thanks to: - -Inspiration for creating a Scheduled Task for Toasts @PaulWetter https://wetterssource.com/ondemandtoast -Inspiration for running Toasts in User Context @syst_and_deploy http://www.systanddeploy.com/2020/11/display-simple-toast-notification-for.html -Inspiration for creating scheduled tasks for the logged on user @ccmexec via Community Hub in ConfigMgr https://github.com/Microsoft/configmgr-hub/commit/e4abdc0d3105afe026211805f13cf533c8de53c4 Version 1.1 - 30/12/20 -Added Snooze Switch option Version 1.0 - 22/07/20 -Release .SYNOPSIS The purpose of the script is to create simple Toast Notifications in Windows 10 .DESCRIPTION Toast_Notify.ps1 will read an XML file so Toast Notifications can be changed "on the fly" without having to repackage an application. The CustomMessage.xml file can be hosted on a fileshare. To create a custom XML, copy CustomMessage.xml and edit the text you want to disaply in the toast notification. The following files should be present in the Script Directory Toast_Notify.ps1 BadgeImage.jpg HeroImage.jpg CustomMessage.xml .PARAMETER XMLSource Specify the name of the XML file to read. The XML file must exist in the same directory as Toast_Notify.ps1. If no parameter is passed, it is assumed the XML file is called CustomMessage.xml. .PARAMETER Snooze Add a snooze option to the Toast .EXAMPLE Toast_Notify.ps1 -XMLSource "PhoneSystemProblems.xml" .EXAMPLE Toast_Notify.ps1 -Snooze #> Param ( [Parameter(Mandatory = $False)] [Switch]$Snooze, [String]$XMLSource = "CustomMessage.xml", [String]$ToastGUID ) #Set Unique GUID for the Toast If (!($ToastGUID)) { $ToastGUID = ([guid]::NewGuid()).ToString().ToUpper() } #Current Directory $ScriptPath = $MyInvocation.MyCommand.Path $CurrentDir = Split-Path $ScriptPath #Set Toast Path to UserProfile Temp Directory $ToastPath = (Join-Path $ENV:Windir "Temp\$($ToastGuid)") #Test if XML exists if (!(Test-Path (Join-Path $CurrentDir $XMLSource))) { throw "$XMLSource is invalid." } #Check XML is valid $XMLToast = New-Object System.Xml.XmlDocument try { $XMLToast.Load((Get-ChildItem -Path (Join-Path $CurrentDir $XMLSource)).FullName) $XMLValid = $True } catch [System.Xml.XmlException] { Write-Verbose "$XMLSource : $($_.toString())" $XMLValid = $False } #Continue if XML is valid If ($XMLValid -eq $True) { #Create Toast Variables $ToastTitle = $XMLToast.ToastContent.ToastTitle $Signature = $XMLToast.ToastContent.Signature $EventTitle = $XMLToast.ToastContent.EventTitle $EventText = $XMLToast.ToastContent.EventText $ButtonTitle = $XMLToast.ToastContent.ButtonTitle $ButtonAction = $XMLToast.ToastContent.ButtonAction $SnoozeTitle = $XMLToast.ToastContent.SnoozeTitle #ToastDuration: Short = 7s, Long = 25s $ToastDuration = "long" #Images $BadgeImage = "file:///$CurrentDir/badgeimage.jpg" $HeroImage = "file:///$CurrentDir/heroimage.jpg" #Set COM App ID > To bring a URL on button press to focus use a browser for the appid e.g. MSEdge #$LauncherID = "Microsoft.SoftwareCenter.DesktopToasts" #$LauncherID = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe" $Launcherid = "MSEdge" #Dont Create a Scheduled Task if the script is running in the context of the logged on user, only if SYSTEM fired the script i.e. Deployment from Intune/ConfigMgr If (([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name -eq "NT AUTHORITY\SYSTEM") { #Prepare to stage Toast Notification Content in %TEMP% Folder Try { #Create TEMP folder to stage Toast Notification Content in %TEMP% Folder New-Item $ToastPath -ItemType Directory -Force -ErrorAction Continue | Out-Null $ToastFiles = Get-ChildItem $CurrentDir -Recurse #Copy Toast Files to Toat TEMP folder ForEach ($ToastFile in $ToastFiles) { Copy-Item (Join-Path $CurrentDir $ToastFile) -Destination $ToastPath -ErrorAction Continue } } Catch { Write-Warning $_.Exception.Message } #Set new Toast script to run from TEMP path $New_ToastPath = Join-Path $ToastPath "Toast_Notify.ps1" #Created Scheduled Task to run as Logged on User $Task_TimeToRun = (Get-Date).AddSeconds(30).ToString('s') $Task_Expiry = (Get-Date).AddSeconds(120).ToString('s') If ($Snooze) { $Task_Action = New-ScheduledTaskAction -Execute "C:\WINDOWS\system32\WindowsPowerShell\v1.0\PowerShell.exe" -Argument "-NoProfile -WindowStyle Hidden -File ""$New_ToastPath"" -ToastGUID ""$ToastGUID"" -Snooze" } else { $Task_Action = New-ScheduledTaskAction -Execute "C:\WINDOWS\system32\WindowsPowerShell\v1.0\PowerShell.exe" -Argument "-NoProfile -WindowStyle Hidden -File ""$New_ToastPath"" -ToastGUID ""$ToastGUID""" } $Task_Trigger = New-ScheduledTaskTrigger -Once -At $Task_TimeToRun $Task_Trigger.EndBoundary = $Task_Expiry $Task_Principal = New-ScheduledTaskPrincipal -GroupId "S-1-5-32-545" -RunLevel Limited $Task_Settings = New-ScheduledTaskSettingsSet -Compatibility V1 -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 600) -AllowStartIfOnBatteries $New_Task = New-ScheduledTask -Description "Toast_Notification_$($ToastGuid) Task for user notification. Title: $($EventTitle) :: Event:$($EventText) :: Source Path: $($ToastPath) " -Action $Task_Action -Principal $Task_Principal -Trigger $Task_Trigger -Settings $Task_Settings Register-ScheduledTask -TaskName "Toast_Notification_$($ToastGuid)" -InputObject $New_Task } #Run the toast of the script is running in the context of the Logged On User If (!(([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name -eq "NT AUTHORITY\SYSTEM")) { $Log = (Join-Path $ENV:Windir "Temp\$($ToastGuid).log") Start-Transcript $Log #Get logged on user DisplayName #Try to get the DisplayName for Domain User $ErrorActionPreference = "Continue" Try { Write-Output "Trying Identity LogonUI Registry Key for Domain User info..." Get-Itemproperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI" -Name "LastLoggedOnDisplayName" -ErrorAction Stop $User = Get-Itemproperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI" -Name "LastLoggedOnDisplayName" | Select-Object -ExpandProperty LastLoggedOnDisplayName -ErrorAction Stop If ($Null -eq $User) { $Firstname = $Null } else { $DisplayName = $User.Split(" ") $Firstname = $DisplayName[0] } } Catch [System.Management.Automation.PSArgumentException] { "Registry Key Property missing" Write-Warning "Registry Key for LastLoggedOnDisplayName could not be found." $Firstname = $Null } Catch [System.Management.Automation.ItemNotFoundException] { "Registry Key itself is missing" Write-Warning "Registry value for LastLoggedOnDisplayName could not be found." $Firstname = $Null } #Try to get the DisplayName for Azure AD User If ($Null -eq $Firstname) { Write-Output "Trying Identity Store Cache for Azure AD User info..." Try { $UserSID = (whoami /user /fo csv | ConvertFrom-Csv).Sid $LogonCacheSID = (Get-ChildItem HKLM:\SOFTWARE\Microsoft\IdentityStore\LogonCache -Recurse -Depth 2 | Where-Object { $_.Name -match $UserSID }).Name If ($LogonCacheSID) { $LogonCacheSID = $LogonCacheSID.Replace("HKEY_LOCAL_MACHINE", "HKLM:") $User = Get-ItemProperty -Path $LogonCacheSID | Select-Object -ExpandProperty DisplayName -ErrorAction Stop $DisplayName = $User.Split(" ") $Firstname = $DisplayName[0] } else { Write-Warning "Could not get DisplayName property from Identity Store Cache for Azure AD User" $Firstname = $Null } } Catch [System.Management.Automation.PSArgumentException] { Write-Warning "Could not get DisplayName property from Identity Store Cache for Azure AD User" Write-Output "Resorting to whoami info for Toast DisplayName..." $Firstname = $Null } Catch [System.Management.Automation.ItemNotFoundException] { Write-Warning "Could not get SID from Identity Store Cache for Azure AD User" Write-Output "Resorting to whoami info for Toast DisplayName..." $Firstname = $Null } Catch { Write-Warning "Could not get SID from Identity Store Cache for Azure AD User" Write-Output "Resorting to whoami info for Toast DisplayName..." $Firstname = $Null } } #Try to get the DisplayName from whoami If ($Null -eq $Firstname) { Try { Write-Output "Trying Identity whoami.exe for DisplayName info..." $User = whoami.exe $Firstname = (Get-Culture).textinfo.totitlecase($User.Split("\")[1]) Write-Output "DisplayName retrieved from whoami.exe" } Catch { Write-Warning "Could not get DisplayName from whoami.exe" } } #If DisplayName could not be obtained, leave it blank If ($Null -eq $Firstname) { Write-Output "DisplayName could not be obtained, it will be blank in the Toast" } #Get Hour of Day and set Custom Hello $Hour = (Get-Date).Hour If ($Hour -lt 12) { $CustomHello = "Good Morning $($Firstname)" } ElseIf ($Hour -gt 16) { $CustomHello = "Good Evening $($Firstname)" } Else { $CustomHello = "Good Afternoon $($Firstname)" } #Load Assemblies [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null #Build XML ToastTemplate [xml]$ToastTemplate = @" <toast duration="$ToastDuration" scenario="reminder"> <visual> <binding template="ToastGeneric"> <text>$CustomHello</text> <text>$ToastTitle</text> <text placement="attribution">$Signature</text> <image placement="hero" src="$HeroImage"/> <image placement="appLogoOverride" hint-crop="circle" src="$BadgeImage"/> <group> <subgroup> <text hint-style="title" hint-wrap="true" >$EventTitle</text> </subgroup> </group> <group> <subgroup> <text hint-style="body" hint-wrap="true" >$EventText</text> </subgroup> </group> </binding> </visual> <audio src="ms-winsoundevent:notification.default"/> </toast> "@ #Build XML ActionTemplateSnooze (Used when $Snooze is passed as a parameter) [xml]$ActionTemplateSnooze = @" <toast> <actions> <input id="SnoozeTimer" type="selection" title="Select a Snooze Interval" defaultInput="1"> <selection id="1" content="1 Minute"/> <selection id="30" content="30 Minutes"/> <selection id="60" content="1 Hour"/> <selection id="120" content="2 Hours"/> <selection id="240" content="4 Hours"/> </input> <action activationType="system" arguments="snooze" hint-inputId="SnoozeTimer" content="$SnoozeTitle" id="test-snooze"/> <action arguments="$ButtonAction" content="$ButtonTitle" activationType="protocol" /> <action arguments="dismiss" content="Dismiss" activationType="system"/> </actions> </toast> "@ #Build XML ActionTemplate (Used when $Snooze is not passed as a parameter) [xml]$ActionTemplate = @" <toast> <actions> <action arguments="$ButtonAction" content="$ButtonTitle" activationType="protocol" /> <action arguments="dismiss" content="Dismiss" activationType="system"/> </actions> </toast> "@ #If the Snooze parameter was passed, add additional XML elements to Toast If ($Snooze) { #Define default and snooze actions to be added $ToastTemplate $Action_Node = $ActionTemplateSnooze.toast.actions } else { #Define default actions to be added $ToastTemplate $Action_Node = $ActionTemplate.toast.actions } #Append actions to $ToastTemplate [void]$ToastTemplate.toast.AppendChild($ToastTemplate.ImportNode($Action_Node, $true)) #Prepare XML $ToastXml = [Windows.Data.Xml.Dom.XmlDocument]::New() $ToastXml.LoadXml($ToastTemplate.OuterXml) #Prepare and Create Toast $ToastMessage = [Windows.UI.Notifications.ToastNotification]::New($ToastXML) [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($LauncherID).Show($ToastMessage) Stop-Transcript } } |
Building the Package
The very nature of a Toast Notification is to “Set and Forget” it. In our script, we are setting the Toast duration to Long which means it will stay open for 25 seconds. Once the Toast disappears it remains in the Notification Center for 3 days. At the moment, I couldn’t think of a suitable way to set a detection method for this script because there is no payload. The best option I have found for now is to deliver the Toasts using packages – arghhh..run for the hills and chase him with a pitch fork. Please contribute if you can think of a better way to do this.
To create the package in MEMCM, you will need the following files in your Package Content Source Directory
- Toast_Notify.ps1
- BadgeImage.jpg
- HeroImage.jpg (364 x 180px, 3MB Normal Connection / 1MB Metered Connection)
- CustomMessage.xml
Absolutely use your own Hero and Badge Image. The dimensions and size for the Hero Image are quite strict. All of these files are available in my Git repository so go grab them for your test labs. You will also find some other Custom Message XML’s too to play with https://github.com/byteben/Toast
1. From the ConfigMgr Console, navigate to Software Library > Packages > Create Package
2. Enter a Name e.g. “Toast Notifications“
3. Select This package contains source files and browse to the content source directory that contains the files listed above
4. Click Next
5. Ensure Standard Program is selected on the Create Program Type page and click Next
6. Enter the following Information:-
Name: Custom Toast Notification
Command Line: PowerShell.exe -File “Toast_Notify.ps1” -XMLOtherSource “\\MyFileServer\Toast Notifications Custom Message\CustomMessage.xml”
Run: Hidden
Program Can Run: Only when a user is logged on
Run Mode: Run with user’s rights

7. Click Next
8. Select This program can run only on specified platforms and select All Windows 10(64-bit)
9. Set Estimated disk space to 52kb
10. Set Maximum allowed run time (minutes) to 15
11. Click Next and then Click Close
You can create multiple programs for the same package if you want to use the predefined XML’s on my Git. Just change the Name and Command Line to identify the correct XMLs to pass to the script.
Deploy to Users
Now all that left to do is deploy our Toast package to our test user group. Remember, we must deploy to a User Collection
1. Right click the newly created Program Custom Toast Notification and choose Deploy
2. Click Browse and choose the User Collection you wish to deploy the Toast Notification to
3. Click Next
4. If you haven’t already done so, choose which Distribution Point or Distribution Point Group to add the package to and Click Next
5. Ensure the Purpose is set to Required
6. Click New in the Assignment Schedule pane and Select Assign Immediately after this event > As soon as possible (or a schedule of your choice)
7. in the Rerun behaviour drop down, Select Always rerun program
8. Click Next four times and then Click Close
At the next User Policy Refresh Interval, your clients should receive the Toast. You can always force a User Policy Notification refresh from your device node in the ConfigMgr console
You can monitor the script deployment in the Ccm32BitLauncher.log file
Summary
That was a whistle stop tour of deploying Toast Notifications with MEMCM. I wanted to give you an introduction into how Toast Notifications are formed and how I deploy them using MEMCM. Most of the complex work is in the script which I hope to develop. If you want to contribute on GitHub, I would be more than happy to work with you on Pull requests and suggestions.
I spent a good chunk of my spare time making it my goal to understand how Toast Notifications work in Windows. This is the end result – a labour of love.
I wouldn’t be doing the community justice if I didn’t mention Martins work on Toast Notifications too. He has really developed a neat solution. Go and check it out https://www.imab.dk/windows-10-toast-notification-script/
Gary Block also has a really neat example in his GitHub repository too and uses Base64 to encode his images – so cool! https://github.com/gwblok/garytown/blob/master/Office365/CI_ToastLaunch_Remediate.ps1
Thanks too to Chris Roberts and Guy Leech for helping with some background work and coding conundrums, I valued your input.
Hi Ben,
This is a fantastic powershell script and corrosponding article, breaking down the layers of Windows notifications. I’ve recently created a similar (but more simple) version and deployed to our fleet. I was wondering if you encountered a problem of notifications being at the forefront during full screen events (such as delivering a presentation via Powerpoint or in a video call in Zoom/Teams etc)?
Once again, well done
James
Hey James, thanks for the feedback. I’ve not tried but toggling the Focus assist / notification settings in Windows may alleviate some of those pain points. The notifications are coming from the MSEdge app in my script so this is where you would need to play in those control panel areas.
Total rip-off from imab.dk
Thanks for the positive feedback.
Martin and I are friends, I can assure you I have not โripped off his workโ.
If you took the time to read the Microsoft articles and understood how Toasts work you will know that Toast Notification creation using the Windows built in assemblies will probably look similar across each coded solution.
Have you not looked at other, similar, solutions before posting your comment?
I have given kudos to Martin and his solution and advised others to check his blog both here and on Twitter – on several occasions.
I took the time to research the technology and code and built the solution from the ground up before taking inspiration from others who have produced Toast scripts to give a personalised Toast experience.
I took the time to create this post as a tutorial for others who are looking for inspiration to create their own Toast solution – not for monetary gain but because I, and many others, value the contributions from the Microsoft community.
As I tell my children, if you have nothing nice to say then donโt say it.
I bid you good day
Hi! Great alternative to Martin’s solution! Thank for your hard work!
…Dietmar
Thanks Dietmar, appreciate that ๐
Thank you Ben for all these great tutorials and explications, You and Martin from imab.dk and also Niall from windows-noob.com, have helped me a lot in understanding SCCM
Thanks Ahmed, one big community ๐
Hi Ben,
When i run this through CM and run the program as user, it does not display the username but when i run it from the CM local cache as user, it shows the username, what could be wrong? Please advise.
Hi Jari,
$LoggedOnUserPath = “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI” LastLoggedOnDisplayName
What is in that Registry key? Perhaps you can run a CMPivot query against your device to check it.
The reg key is there and the script works when running from local CM cache as user but when running from CM as user it doesn’t seem to get the regkey info. (CM packet is copied to local cache)
I have tried with two different computers and two different users.
Hello
i have the same problem here
did you have a solution
the powershell script did not create any error messages
That’s odd – I haven’t been able to reproduce this. Can you deploy a simple script to the user that reads that registry key and output the result to a TXT file? That way you can isolate it being CM not able to read from that registry key.
You can use this code to get the GivenName instead
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::Domain, [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain())
$GivenName = ([System.DirectoryServices.AccountManagement.Principal]::FindByIdentity($PrincipalContext,[System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName,[Environment]::UserName)).GivenName
$GivenName
For Domain joined devices yes ๐
Hi Ben,
This is awesome, thank you, it’s taken me a few reads to try and understand it and due to being a bit slow i still don’t, however i really appreciate what you went through from a learning perspective to do this.
I’ve been testing on a Windows 10 1909 machine today, and i found that i couldn’t get the duration to work unless i removed the scenario ‘Reminder’, once removed the ‘short’ and ‘long’ duration works perfectly.
After a bit of googling (i’m sure i’m doing something wrong) but i found that the options it will accept are Alarm or Incoming call, what have i missed?
This isn’t an issue i’m just interested, it works beautifully though, again thank you.
Regards
James
https://docs.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-commands
Great guide!
But I have one question – how to display toast message not just 5 seconds but indefinitely until user clicks on it?
The default Toast Timeout in my script is “Long” which is 25 seconds.
#ToastDuration: Short = 7s, Long = 25s
$ToastDuration = “long”
You could try the toast scenario = reminder which should keep the notification displayed until the user interacts with it.
toast scenario=”reminder”
https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-schema#toastscenario
Thanks for the article. Learned a lot reading it.
Please keep up the good work. Have a nice one
Hi,
sorry but I’m lost at the first steps…
How can i test the first simple notification?
I don’t understand the command to execute it
Thanks!!
The first example is a toast XML layout and not the full script to deploy the toast. If you look at the main script you will see the assemblies that need loading at lines 125-131. Lines 132-162 is the toast layout and from line 164 the toast is compiled and displayed to the current user.
So create a new script including lines 125-131, replace line 132-162 with the simple toast XML layout in the first example and then include from line 164.
That should give you a simple toast. Iโm on my phone right now but will create a full example of a simple toast later
Thanks I did it..
Another question if I can…
I’d like to $customhello with name istead that with surname..how can i goal this?
Then do I need to set the
$Launcherid = “MsEdge”
to make it work…Is it pobbible to modify it?
I have a question about packaging this script in SCCM. I’m confused on what my command line should look like. All the proper files are on the same file share. So should my command line look like this – powershell.exe -File Toast_Notify.ps1 -XMLScriptDirSource โCustomMessage.xmlโ or should it look like powershell.exe -File Toast_Notify.ps1 -XMLOtherSource “\\MyFileServer\Toast Notifications Custom Message\CustomMessage.xmlโ ?
Hi Nick,
It depends if the XML file is in the same content source directory as your script. If you want to change the XML “on the fly” without updating the content, I would use the later example in your question. If the XML content is quite static, then include it in the same content directory as your script in your first example.
Hello Ben,
I followed this article with great interest. I have been trying to get it to work without installing config manager as to be honest, I cannot seem to get it installed on my app server, it keeps throwing obstacles my way!
So my question is, is there a way to make this work without using config manager, to deliver the notifications to domain users via GPO / Task Scheduler or something?
Through my testing, it seems to work fine if the files are saved locally, however when I place them on a file share, the toast notification appears, but doesn’t display properly. Basically I just want to post COVID-19 reminders to all staff.
Any help appreciated, the toast itself is awesome.
Thanks for the feedback. As long as the client can see the XML on your network server the toast should display correctly. Are the images available locally?
You donโt have to use ConfigMgr to deploy toasts. Just remember that whatever mechanism you use it must run in the users context.
If you DM me @byteben on Twitter with your toast not displaying correctly I may be able to offer some advice.
Ben
Hi Ben,
I was tinkering with this today and have managed to get it working, just one final thing holding me back.
Basically I stored all the files for the toast in a share in which users have read access. I deployed the share hidden via Group Policy. It wasn’t working for me when I had the share within the task using a UNC task, so after deploying to use the Z:\ map letter, it has worked. Not sure why it didn’t like the UNC path but did like the mapped drive.
I then managed to get a task scheduled and sent via Group Policy as well, perfect! One last thing though, I added a couple of things to the .PS1 arguments:
-windowstyle hidden
I am trying to get that pesky blue screen to stop showing at all. Everything i have read is pointing to having it set to “Run whether the user is logged on or not”. I have tried doing this with a dedicated services account and unfortunately cant get past that stage. When I don’t launch it as the domain user who is currently logged in, it either wont deploy the task or the task doesn’t launch.
Any ideas on that one?
I would DM you, but I don’t have any social media accounts! Cheers
Hi Ben,
Wrote out a big reply and thought I hit send! Sigh..
Basically got it working, I deployed a mapping to a share and had to use a drive letter, for some reason the full unc path route was not working. So I deployed the “Z” drive hidden so users wont see a difference.
Literally the only minor issue now is a blue powershell window pops up for around .5 of a second or so before the toast is delivered. I have been reading that I need to enable the option to “Run whether the user is logged in or not” however I have tried this, but you need to enter admin credentials and upon doing so and changing the user account that launches the task, the task no longer works.
Not a major issue, but overall the toast looks great and works well, the minor blue window moment is just getting to me lol! -windowstyle hidden in arguments of the task does only make it show briefly, but doesn’t get rid completely.
Hi Iain,
If you choose “Run whether the user is logged in or not” it will run as SYSTEM. The toasts must be run in the user context – so I would expect that behaviour.
When you run powershell.exe the OS always launches a console first and then hides it based on the -windowstyle hidden parameter passed. I haven’t spent much time focusing on this behaviour or a workaround. Have you looked at Martin Bengtsson’s solution over at https://www.imab.dk/windows-10-toast-notification-script/ He may be doing something funky – his solution is quite impressive ๐
DMs are open @byteben on Twitter ๐
Hi there,
Great explanation! I had a question, if I wanted to use in the parameter -XMLOtherSource a web link to a XML file, how could I achieve that? I have the link, I can access the link and see the content but I cant use it in “” as paremeter.
Cheers!
Hey, thanks for the kind words.
To read an XML from a URL you would have to use the XMLDocument.Load method. If I get time Iโll add this as an option but you might be able to figure it out yourself using the link below.
https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.load?view=net-5.0
So maybe it’s something I’m not understanding, but basically, for every new announcement, I have to create a new package with my new xml message, and then deploye it?
Hi, I followed your guide and created an SCCM package to deploy. I can see the files get copied into the desktop but the script fails to run. Looking at SCnotify logs, I see there is an error
Getting a WMI instance for CCM_Program.PackageID=”ATA00206″,ProgramID=”Toast” (Microsoft.SoftwareCenter.Client.Data.WmiConnectionManager at GetInstance)
The specified object could not be found in WMI. (Microsoft.SoftwareCenter.Client.Data.WmiConnectionManager at GetInstance)
If I run the command locally where SCCM copied the files, it runs fine. So why is it not running via the SCCM agent ? Any suggestions? Should I try using a different app instead of Edge ?
I want to add Snooze timer , Can you please help me with that .
Hi, I have updated the code to include a Snooze option, check it out https://github.com/byteben/Toast
Toast_Notify.ps1 and CustomMessage.xml have been updated
Blog coming soon to give advice on how the Toast Schedule works.
Ben
Hi,
First of all thank for the guide it’s awnsome.
I have same issue as Nitin:
Hi, I followed your guide and created an SCCM package to deploy. I can see the files get copied into the desktop but the script fails to run. Looking at SCnotify logs, I see there is an error
Getting a WMI instance for CCM_Program.PackageID=โATA00206โณ,ProgramID=โToastโ (Microsoft.SoftwareCenter.Client.Data.WmiConnectionManager at GetInstance)
The specified object could not be found in WMI. (Microsoft.SoftwareCenter.Client.Data.WmiConnectionManager at GetInstance)
Some solution for this?
Hi, I donโt really understand whatโs happening from that small log snippet. Do you have the full log?
Have you also checked this article ?
https://docs.microsoft.com/en-us/mem/configmgr/core/clients/deploy/about-client-settings#enable-user-policy-on-clients
Solved!!! my problem was that We have a maintenance windows and I didn’t allow to be performed outside maintenance window.
Sorry….
Hi Ben,
I’ve found a few of these toast articles around the Internet, and I must say yours is exceptionally well presented. Thank you.
I was (am) struggling with getting the assemblies to load. I’ve now discovered it’s because I am running PowerShell 7.1 (core).
Just for Google’s bots, the error message I get is:
Unable to find type [Windows.UI.Notifications.ToastNotificationManager,Windows.UI.Notifications, ContentType=WindowsRuntime].
and
Unable to find type [Windows.Data.Xml.Dom.XmlDocument,Windows.Data.Xml.Dom.XmlDocument, ContentType=WindowsRuntime].
I’ve found a similar issue on SO: https://stackoverflow.com/questions/65680941/powershell-unable-to-find-type-windows-ui-notification. Not answer, but a comment alludes to “I should add winRT manually”. I don’t know what that is.
Thank you.
Hi Adrian,
Thanks for the feedback!
You are correct, built in support in PowerShell 7.1 has been removed for WinRT assemblies. Martin Bengtsson first brought it to my attention and we found the following link confirmed it. I have it on my list to investigate but can’t give any advice at this stage. Do you force PowerShell 7.1 on all your clients?. See https://github.com/dotnet/runtime/issues/37672
Do you know how to remove a toast before the new one is popped up?
My problem is that my toast pops up Friday, Saturday and Sunday, so on Monday they have 4 notifications. I’d like my script to remove the old ones before popping up the new one. It is telling them they need to restart, so no new information is added.
Hi David,
You will need to specify a tag to the Toast to call it for update
https://docs.microsoft.com/en-us/uwp/api/windows.ui.notifications.toastnotifier.update?view=winrt-19041#Windows_UI_Notifications_ToastNotifier_Update_Windows_UI_Notifications_NotificationData_System_String_
So normally when we create a new toast we do this: –
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($LauncherID).Show($ToastMessage)
We would maybe do this to update a toast: –
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($LauncherID).Update($ToastMessage, tag, group)
I haven’t played with this yet and am just making assumptions from the docs. I’ll add it to my dev list for the script. Thanks
Can this be done while i am using just a PowerShell script and XML file?
today – no. I’m working on embedding the images as base64 encoded. Stay tuned ๐
Hi Ben,
Nice little topic ๐ I’m interested in the second picture (Your organization requires your device to restart in 7 days), with the ‘Pick a time’ option. Do you mind sharing the xml?
Thanks ๐
Hi, thank you for the good work, really cool.
However, I am not getting any toast pop up and also not in Action Center. I followed your guide and I can see in the log that something is happening, but no dice. ๐
The Ccm32BitLauncher.log shows this:
SessionId = 1; SystemContext = 0; Interactive = 1; UseNAA = 0 Ccm32BitLauncher 13-04-2021 15:58:20 22736 (0x58D0)
Commandline: “C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe” -File Toast_Notify.ps1
Working directory: C:\WINDOWS\ccmcache\ax\
Interactive: 1
Window mode: 0 Ccm32BitLauncher 13-04-2021 15:58:20 22164 (0x5694)
Launched process 9812 for installation Ccm32BitLauncher 13-04-2021 15:58:20 22164 (0x5694)
Hi Ben,
Many thanks for your article.
Please can you advise me on how I can deploy it by intune?
Cheers
Hi! Is it possible to show toast notification on windows 8.1?
Do I need to deploy a toast powershell module to each machine or SCCM server or will it work without the module?
This solution is a single script – no module installation necessary
I’m also very curious on how to deploy this using Intune. There are various other devs using this with other projects and I’m having a bit of a challenge incorporating this into their methods.
Hi Ben, Thanks for the effort and sharing the script. Wonder if you already have one to share on fetching the XML on a cloud URL?
hi why when I run script from file share for example powershell.exe -File “Toast_Notify.ps1” -XMLSource “\\MYSERVER\Software\CustomMessage.xml” I get an error message :
\\MYSERVER\Software\CustomMessage.xml is invalid.
At C:\Noti\Toast_Notify.ps1:102 char:5
+ throw “$XMLSource is invalid.”
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (\\MYSERVER\Softw…xml is invalid.:String) [], RuntimeException
+ FullyQualifiedErrorId : \\MYSERVER\Software\CustomMessage.xml is invalid.
Hello,
Very nice documentation and helped me to create new toast notification.
When I run the PS, toast notification appears correctly, however when I deploy it thru SCCM, it updates the log but it only gives “Windows PowerShell…New Notification” notification but text content / images etc. is missing. Where can be missing?
Hi Ben
Thank you for taking the time to write this script. I have been looking for such a solution for a long time and I appreciate all the time and the energy you have devoted to this project.
Can this script be deployed via Microsoft Endpoint Manager (Intune)? I have created a Win32 app, added the script, the XML file and the pictures and I use the following run command:
powershell.exe -ex bypass -file .\Toast_Notify.ps1 -XMLSource “CustomMessage.xml”
I have also tried without the -XML source and even though it ends up on a test machine (mine), nothing happens.
Do you have any idea what I am doing wrong?
Again: Thanks for all the time you have invested in this great little tool/script.
Best wishes
Massimo
Great work Ben. This is amazing stuff!
Question: Do you know of a way to send a toast notification that could also receive input from the user? I’m wanting to validate some asset management information on each device and need a method to ask users logged on to verify or input data into a field if possible.
Thank you.