Working with Toast Notifications and using PowerShell scripts as button actions

Toast notifications are a type of unobtrusive and visually appealing pop-up notification that appears on the screen of a Windows device. They are designed to grab the user’s attention without interrupting their workflow. The term “toast” is derived from the way these notifications resemble a slice of toast popping up from a toaster.

Toast notifications provide a way for applications and the operating system to deliver important information or alerts to users in a non-intrusive manner. They typically appear in a corner of the screen, often the bottom-right corner, and display a brief message along with an optional icon or image.

It seems that it becomes more popular in enterprise environments to inform the users to reboot their device, maybe in order to clean up the memory, install the updates, etc. So in a previous post I have explained how to get the correct reboot time of the OS independently if Fast Boot is enabled or not, so naturally I imagined “hey, how hard can it be to create a toast notification that reboots the device when the users presses a button?”. As I was about to learn, it’s actually not that simple.

To start, I wanted to first create a simple toast notification just to see how it looks and how it’s done. This part was not hard at all as I found some documentation regarding the Toast Notification Manager on Microsoft website. I even searched a bit more and found this nice toast repository which contains examples for all types of the toast notifications you can create.

In my case I needed two buttons, so I figured out that the binding template that I had to use is the “ToastGeneric” one, so I grabbed this example over here:

$xml = @”
<toast>
<visual>
<binding template=”ToastGeneric”>
<text>Music Player</text>
<text>Download Finished</text>
</binding>
</visual>
<actions>
<action content=”Play” activationType=”protocol” arguments=”C:\Windows\Media\Alarm01.wav” />
<action content=”Open Folder” activationType=”protocol” arguments=”file:///C:/Windows/Media” />
</actions>
</toast>
“@
$XmlDocument = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]::New()
$XmlDocument.loadXml($xml)
$AppId = ‘{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe’
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]::CreateToastNotifier($AppId).Show($XmlDocument)

 

So let us break down the code:

  1. XML Template (Toast Notification Definition): The $xml variable holds the XML definition of the toast notification. It’s formatted using a Here-String (enclosed by @" and "@). The XML structure defines the appearance and content of the notification.
    • <toast>: The root element of the toast notification.
    • <visual>: Specifies the visual layout of the toast notification.
    • <binding template="ToastGeneric">: Defines a generic layout for the toast notification.
    • <text>: Contains the text content of the notification. In this case, there are two <text> elements: “Music Player” and “Download Finished.”
    • <actions>: Defines the action buttons that appear below the notification content.
    • <action>: Specifies an action button. Each <action> element has attributes content, activationType, and arguments. The content attribute defines the button label, activationType specifies the type of action, and arguments holds the arguments passed when the action is triggered.
  2. Creating an XML Document: The $XmlDocument variable is created using the Windows Runtime type [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]::New(). This creates an instance of an XML document that will be used to load the toast notification XML content.
  3. Loading XML Content: The loaded XML content is then assigned to the $XmlDocument using the .loadXml($xml) method. This fills the XML document with the contents of the defined $xml string.
  4. App ID: The $AppId variable is set to an identifier that corresponds to PowerShell. This ID is used to associate the toast notification with the PowerShell application.
  5. Displaying the Toast Notification: The last line uses the [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]::CreateToastNotifier($AppId).Show($XmlDocument) method chain to create a toast notifier for the specified app ID and then show the toast notification using the XML content defined in the $XmlDocument.

 

So, if we go an copy-paste the code into PowerShell ISE and run it, POOF, it works like a charm:

Cool! First I wanted to do some changes like adding an image, make it a bit nicer. Apparently there are some options as stated in the Microsoft documentation so first I tried to add:

<image placement=”appLogoOverride” hint-crop=”circle” src=”C:\Windows\IdentityCRL\WLive48x48.png”/>

Well, that worked like a charm and it was easy! I was getting quite confident that in a couple of minutes everything will be solved. Then I though, hey…but wait, if I want to deploy this I would want to deploy it as a simple script without additional files (like an image) aside it. So I though hey, PowerShell offers the possibility to encode and decode base64 strings..and guess what you can encode in base64? You guessed it..Images.

Because I am lazy I went to https://www.base64-image.de/ and uploaded my logo and grabbed the base64 encoding:

Then, I have added all the base64 data into a variable and I thought that only by converting from Base64 with PowerShell it will work:

Oh but was I wrong…I tried multiple methods on providing the data in the belief that the XML will parse the data as it will parse an image..then it hit me..why am I not writing the image somewhere on the user device..like in the %temp% folder? So i’ve done that an voila, it works!

The code that I added is:

$AlkaneBase64String = “YOURBASE64DATA”

$filename = $env:TEMP + “\notification.png”

$bytes = [Convert]::FromBase64String($AlkaneBase64String)
[IO.File]::WriteAllBytes($filename, $bytes)

 

I also modified the scr in the XML to point to $filename:

<image placement=”appLogoOverride” hint-crop=”circle” src=”$filename”/>

 

Good, so far so good, all that is left to do is modify the buttons and the text. The text is easy, this is modifiable in the XML.

Cool, so the text is there as we want (ignore the bad hours, it was the old script), so let us tell the Reboot button to restart the machine…….wait….how the hell do we do that? 🤯🤯🤯🤯🤯🤯

First I thought that is at easy as setting the argument as a PowerShell command:

arguments=”Restart-Computer -Force”

But that didn’t work….Then I thought we can point it to the shutdown executable present in Windows. The path to it is %windir%\System32\shutdown.exe. To restart a device with the shutdown utility is quite easy, the command line is shutdown /r /f /t 0, let me explain:

  • /r – tells the utility that we want to restart
  • /f – forces the action
  • /t – the amount of time before the action is executed. in our case we set it to 0 because we want the action to be instantaneous

But…this didn’t work as well. The next thought was to write this into a .cmd file and place it as an argument:

arguments=”c:\programdata\restart.cmd”

But…this didn’t work. Ok let us then try with a PowerShell script:

arguments=”c:\programdata\restart.ps1″

Don’t know why exactly I thought it will work with PowerShell since it was obvious watching the processes that the action will not execute. I started to read again the Microsoft Documentation regarding the action..maybe I missed something. In the description of the arguments it is mentioned:

“App-defined string of arguments that the app will later receive if the user clicks this button.”

Digging a bit deeper, it seems that if you do this with C# you can place a name of a certain function that you later on define in your code..but with PowerShell I did not find such a solution.

Of course I kept trying and I almost thought it is not possible to do this…but then I saw some other examples on the previous github repo…and for the “Open Folder” button example you have the following:

<action content="Open Folder" activationType="protocol" arguments="file:///C:/Windows/Media" />


And when you click the button..it works..it opens the Windows Explorer. In my mind the instant words were: “NO WAY…COULD THIS BE?”. Then I thought, “let me try with HTTP://”…and guess what? It worked:

You are probably asking now..Alex…what the hell does this have to do with anything? Hear me out…: URL schemes!

URL schemes, also known as URI (Uniform Resource Identifier) schemes or protocol handlers, are a way to launch applications or perform specific actions within applications by clicking on hyperlinks or typing URLs into a web browser’s address bar. While URL schemes are commonly associated with mobile operating systems like iOS and Android, they are also used in Windows to provide similar functionality.

In Windows, URL schemes allow you to interact with specific apps or perform actions by using specially crafted URLs. When you enter a URL with a registered scheme in the browser or another app, Windows understands which app to open or which action to perform based on the scheme.

Key points about URL schemes in Windows:

  1. Custom Actions:
    Developers can register their applications to respond to custom URL schemes. For example, a music player app might register a scheme like “musicplayer://” to open its app when a user clicks on a link with that scheme.
  2. Launching Apps:
    URL schemes can be used to launch specific apps directly. For example, typing “mailto:example@example.com” in a browser’s address bar will open the default email client with a new email to “example@example.com.”
  3. Passing Data
    URL schemes can pass data to apps as parameters. For instance, a news app might handle URLs like “newsapp://article?id=123” to open a specific news article.
  4. Custom Actions Within Apps:
    Apps can also define custom URL schemes to perform actions within the app. For example, a note-taking app might define a scheme like “notes://add?text=Hello” to add a new note with the content “Hello.”
  5. Windows Registry:
    To register a URL scheme for an application, developers typically need to modify the Windows Registry. This registration ensures that when a user clicks on a URL with that scheme, Windows knows which app to launch.

As an example:
– `”http://”` and `”https://”` are common URL schemes for web browsing.
– `”mailto:”` launches the default email client.
– `”tel:”` dials a phone number.
– `”ms-settings:”` opens the Windows Settings app.

In summary, URL schemes in Windows provide a way for apps to communicate with each other and offer specific functionalities to users through hyperlinks. They enhance user experience by allowing direct interactions between apps and enabling quick access to specific actions. However, it’s essential to use and interact with URL schemes cautiously to ensure security and privacy.

 

I was excited, I felt like I have broken the Matrix…but then I thought…”wait…how do I do a custom URI? 😐😐😐😐😐”. Apparently, it is not that hard, the information is stored in the registry, so what I did first is this:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\toastprotocol]
@=”URL:PowerShell Protocol”
“URL Protocol”=””

[HKEY_CLASSES_ROOT\toastprotocol\DefaultIcon]
@=”powershell.exe,1″

[HKEY_CLASSES_ROOT\toastprotocol\shell]

[HKEY_CLASSES_ROOT\toastprotocol\shell\open]

[HKEY_CLASSES_ROOT\toastprotocol\shell\open\command]
@=”C:\\ProgramData\\toast\\ToastScript.cmd %1″

 

Inside the ToastScript.cmd I have only written the shutdown command explained earlier just to see if it works..and guess what..IT WORKS! To define it in the XML is quite easy:

<action content=”Reboot” activationType=”protocol” arguments=”toastprotocol://dosomething” />

The moment I clicked the Reboot button, the virtual machine restarted, I was happy! The next part was to figure out how to parse a PowerShell script inside the ToastScript.cmd, so I figured we should play with the %1. In the Windows Registry, when referring to command registry keys, the `%1` is a placeholder that represents a command-line parameter. It is used to pass data or arguments to an application when it is executed using that command.

When you see `%1` in a command registry key, it indicates that the command is designed to receive additional information, typically from a file or URL, when the associated application is launched. The actual value of `%1` is determined dynamically based on what is being passed to the command.

For example, let’s say you have a registry key associated with a text editor. If you double-click a text file, Windows will use the command specified in the registry to open that file in the text editor. The `%1` placeholder in the command string will be replaced with the path to the specific text file you clicked.

Here’s a simplified example of a command registry key with `%1`:

HKEY_CLASSES_ROOT\textfile\shell\open\command
(Default) = “C:\Path\To\TextEditor.exe” “%1”

When you double-click a text file, Windows will execute the command:

“C:\Path\To\TextEditor.exe” “C:\Path\To\Your\File.txt”

In this case, `%1` gets replaced with the full path to the text file you clicked, allowing the text editor to open that specific file.

So, `%1` is a placeholder that ensures an application can receive and process additional information or files when launched through the Windows shell or other means.

The natural thinking was to place the command in the ToastScript.cmd as such:

powershell.exe -WindowStyle hidden -executionpolicy bypass -NonInteractive -NoLogo -NoProfile -Command “& ‘%1’\

But there is a problem…this didn’t work. Then I thought ok…wait..this is a protocol, I can activate it via the Run and see what is happening:

And I think we have found the fault in my logic, the %1 passes all the command along with the protocol name:

But that is ok, we can simply use the Trim and Replace to edit out text since this will be the same always:

powershell.exe -WindowStyle hidden -executionpolicy bypass -NonInteractive -NoLogo -NoProfile -Command “& ‘%1’.Replace(‘toastprotocol://’, ”).Trim(‘/’)”

And voila, it works like a charm! I have created a PowerShell script called test.ps1 placed in C:\ProgramData\toast. The only thing the script does is to open the Downloads folder by using the Invoke-Item cmdlet:

Invoke-Item $env:USERPROFILE\Downloads\

Now all that is left to do is modify the XML:

 <action content=”Reboot” activationType=”protocol” arguments=”toastprotocol://C:\ProgramData\toast\test.ps1″ />

Now when I click the Reboot button, the XML parses the protocol and runs the CMD, which runs the PowerShell script, which opens the Download folder…MADNESS! But it works!

So, this is how you put a PowerShell script as an action to a button for a Toast Notification in Windows.

Leave a comment

Your email address will not be published. Required fields are marked *

nineteen − two =