Monitoring with PowerShell: Notifying users of Windows Updates

With my recently released RunAsUser module there’s been an influx of questions on what it could be used for. I’ve tried to describe as much as possible on the github page and the previous blog about it. But one I wanted to talk about real quick is the ability to create Toast notifications.

Toast notifications are those little OS native notifications you side in the bottom right of your screen when receiving an e-mail. Our RMM system has the ability to create a notification using an application, but to be honest that notification looks like it came straight out of 1990.

To have a bit better user experience, and to also get the ability to do specific things with user-input I’ve decided to use Burnt Toast. Burnt Toast is a module that give you the ability to generate pretty toast messages with just a couple lines of code. Brilliant really!

Combining my RunAsUser module, and Burnt Toast we’re able to send a script to the currently logged on user’s session and get full functionality in there. One example is to reboot the computer after updates. So lets get going!

The script

The following script can be used to create a toast for reboots. It creates a ‘protocol handler’. It then toasts with a nice Gif of my logo to get the users attention. The script assumes you have trusted the PSGallery before hand.

#Checking if ToastReboot:// protocol handler is present
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -erroraction silentlycontinue | out-null
$ProtocolHandler = get-item 'HKCR:\ToastReboot' -erroraction 'silentlycontinue'
if (!$ProtocolHandler) {
    #create handler for reboot
    New-item 'HKCR:\ToastReboot' -force
    set-itemproperty 'HKCR:\ToastReboot' -name '(DEFAULT)' -value 'url:ToastReboot' -force
    set-itemproperty 'HKCR:\ToastReboot' -name 'URL Protocol' -value '' -force
    new-itemproperty -path 'HKCR:\ToastReboot' -propertytype dword -name 'EditFlags' -value 2162688
    New-item 'HKCR:\ToastReboot\Shell\Open\command' -force
    set-itemproperty 'HKCR:\ToastReboot\Shell\Open\command' -name '(DEFAULT)' -value 'C:\Windows\System32\shutdown.exe -r -t 00' -force
}

Install-Module -Name BurntToast
Install-module -Name RunAsUser
invoke-ascurrentuser -scriptblock {

    $heroimage = New-BTImage -Source 'https://media.giphy.com/media/eiwIMNkeJ2cu5MI2XC/giphy.gif' -HeroImage
    $Text1 = New-BTText -Content  "Message from IT"
    $Text2 = New-BTText -Content "Your IT provider has installed updates on your computer at $(get-date). Please select if you'd like to reboot now, or snooze this message."
    $Button = New-BTButton -Content "Snooze" -snooze -id 'SnoozeTime'
    $Button2 = New-BTButton -Content "Reboot now" -Arguments "ToastReboot:" -ActivationType Protocol
    $5Min = New-BTSelectionBoxItem -Id 5 -Content '5 minutes'
    $10Min = New-BTSelectionBoxItem -Id 10 -Content '10 minutes'
    $1Hour = New-BTSelectionBoxItem -Id 60 -Content '1 hour'
    $4Hour = New-BTSelectionBoxItem -Id 240 -Content '4 hours'
    $1Day = New-BTSelectionBoxItem -Id 1440 -Content '1 day'
    $Items = $5Min, $10Min, $1Hour, $4Hour, $1Day
    $SelectionBox = New-BTInput -Id 'SnoozeTime' -DefaultSelectionBoxItemId 10 -Items $Items
    $action = New-BTAction -Buttons $Button, $Button2 -inputs $SelectionBox
    $Binding = New-BTBinding -Children $text1, $text2 -HeroImage $heroimage
    $Visual = New-BTVisual -BindingGeneric $Binding
    $Content = New-BTContent -Visual $Visual -Actions $action
    Submit-BTNotification -Content $Content
}

And that’s it! you must be wondering how it looks, so lets show you that too!

And that’s it! as always, Happy PowerShelling!

37 Comments

  1. Justin July 19, 2020 at 8:50 pm

    Hi Kelvin!

    As always, thank you for your amazing scripts. I am working on getting this one working, it was not working for me, so I added a start-transcript command and stop-transcript to capture what was gong on. Below is the output I am getting when running this using the 64-bit version of powershell as the SYSTEM user through my RMM. Any ideas on this?

    invoke-ascurrentuser : Could not execute as currently logged on user: Exception calling “StartProcessAsCurrentUser”
    with “4” argument(s): “CreateProcessAsUser failed. (Access is denied, Win32ErrorCode 5 – 0x00000005)”
    At RebootNotifications.ps1:25
    char:1
    + invoke-ascurrentuser -scriptblock {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], MethodInvocationException
    + FullyQualifiedErrorId : System.Management.Automation.MethodInvocationException,Invoke-AsCurrentUser
    invoke-ascurrentuser : Could not execute as currently logged on user: Exception calling “StartProcessAsCurrentUser”
    with “4” argument(s): “CreateProcessAsUser failed. (Access is denied, Win32ErrorCode 5 – 0x00000005)”
    At RebootNotifications.ps1:25 char:1
    + invoke-ascurrentuser -scriptblock {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], MethodInvocationException
    + FullyQualifiedErrorId : System.Management.Automation.MethodInvocationException,Invoke-AsCurrentUser

    Any help you can provide would be most appreciated!

    Cheers!

    JustMirsk

    1. Kelvin Tegelaar July 19, 2020 at 9:11 pm

      This really looks like it either couldn’t access the PowerShell executable, or that it could not create a process due to not having enough permissions.

      What RMM are you using? could you try executing this with your RMM:

      $pwshPath = (Get-Process -Id $pid).Path
      write-host $pwshPath

      This should show the command it tries to pass to RunAsUser.

      1. Justin July 20, 2020 at 12:21 am

        Thanks for the really fast reply! Here is the output from the script, I ran a start and stop transcript so you can see everything that gets output.

        **********************
        Transcript started, output file is c:\temp\powershell.log
        C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
        **********************
        Windows PowerShell transcript end
        End time: 20200719181625
        **********************

        I don’t see anything specifically wrong here. I have other powershell scripts that do run as SystemUser (at least I have them set to run as System User and they execute with elevated privileges without having to provide credentials etc).

        I am running a lesser known RMM, Naverisk. Naverisk natively will run PowerShell with C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe. I am specifically calling my powershell scripts with a batch file using the command of:

        %SystemRoot%\sysnative\WindowsPowerShell\v1.0\PowerShell.exe -ExecutionPolicy Bypass -Command “Path to my PS1”

        1. luis July 23, 2020 at 4:34 pm

          Same here using Powershell on my computer.
          invoke-ascurrentuser : Could not execute as currently logged on user: Excepción al llamar a “StartProcessAsCurrentUser” con los argumentos “5”: “WTSQueryUserToken failed to get access
          token. (El cliente no dispone de un privilegio requerido, Win32ErrorCode 1314 – 0x00000522)”
          En línea: 16 Carácter: 1
          + invoke-ascurrentuser -scriptblock {
          + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          + CategoryInfo : NotSpecified: (:) [Write-Error], MethodInvocationException
          + FullyQualifiedErrorId : System.Management.Automation.MethodInvocationException,Invoke-AsCurrentUser

          Do you have an EDR installed?
          Thanks

          1. luis July 23, 2020 at 4:35 pm

            Great script BTW 🙂

          2. Kelvin Tegelaar July 23, 2020 at 4:55 pm

            0x00000522 means a privilege cannot be obtained, so the token cannot be copied. This might be EDR, it could also be some permissions have been removed from the SYSTEM account.

  2. Dan July 20, 2020 at 1:21 am

    Is there a way for that notification to stay until a user accepts it?
    Too often users can claim they didn’t see the notification.

    1. C.J. May 6, 2021 at 5:47 pm

      You can add -Scenario IncomingCall to your New-BTContent and that should make it persist until they user interacts with it.

  3. Tom July 20, 2020 at 8:33 am

    Hi Kelvin,

    Thanks for another great script!

    I’ve seen that you use N-Central and I was wondering how you would use this to replace the ugly pop-ups they use? I don’t know how you could detect if any patches were installed and if so, then run this command. Or do you just run it at a set time after the install element of a maintenance window?

    Thanks,
    Tom

    1. Kelvin Tegelaar July 20, 2020 at 9:10 am

      We have maintenance policy without N-Central generated pop-up. We then check if updates have been installed using a monitoring script, and as self-healing run the script above. 🙂

  4. Patrick July 24, 2020 at 7:29 pm

    When I run the script I get the PID returned to me, but nothing happens on screen. No errors from running the script. Any ideas?

    1. Kelvin Tegelaar July 24, 2020 at 8:48 pm

      the script doesn’t give any output directly, you’ll have to return the output from the script by redirecting it with |out-file or transcribing 🙂

      1. Patrick July 26, 2020 at 4:01 pm

        I think I was confusing in what I was trying to describe, the toast notification does not come across for the user.

        1. Trey February 15, 2021 at 4:33 am

          Hey Patrick,
          I’m not sure if you’re still having this issue but I was able to resolve it on my RMM by utilizing the invoke-ascurrentuser at the end of script, with the contents of all the Toast notifications as the scriptblock.

          So it effectively looks like this:
          $scriptblock = { }
          invoke-ascurrentuser -NonElevatedSession -scriptblock $scriptblock

          I’m not certain why this allows it to work properly instead of outputting the PID.

  5. Shaun August 12, 2020 at 11:08 am

    Just to clarify , does this alert know to prompt when a windows update requires rebooting ? is it aware that updates are pending ?

    Thanks

    1. Kelvin Tegelaar August 12, 2020 at 11:14 am

      No, this just pop-ups the notification. You’ll have to use your RMM for the detection part. 🙂

  6. Travis Phipps August 17, 2020 at 3:53 am

    So excited to use this but I’m having some of the same issues others have mentioned.
    Specifically, when running this from a SYSTEM session, I get the PID returned very quickly, but nothing ever appears in the user session.
    When I run from the user session, it works great.

    Right now I’m testing using the backstage feature in CW Control before moving this into a CW Automate script.

    Any thoughts or ways I can trap further error/issue information?

    1. Kelvin Tegelaar August 17, 2020 at 8:49 am

      Hi Travis,

      You can try a start-transcript in your scriptblock, so you can see exactly what happens during the running of the script.

      Another option is to install the latest version of “RunAsUser” and use the switch “-UseWindowsPowershell”. I’ve seen some RMMs execute PowerShell as part of a process of another executable, so it would fail to run. Hope that helps!

  7. Sahil September 29, 2020 at 2:23 pm

    Hi Kelvin I really appreciated for this Script.

    I run the commands on powershellISE . I got the error.
    Please Have look on the error message.
    Name Property
    —- ——–
    command
    invoke-ascurrentuser : The ‘invoke-ascurrentuser’ command was found in the module
    ‘RunAsUser’, but the module could not be loaded. For more information, run ‘Import-Module
    RunAsUser’.
    At line:16 char:1
    + invoke-ascurrentuser -scriptblock {
    + ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (invoke-ascurrentuser:String) [], CommandNo
    tFoundException
    + FullyQualifiedErrorId : CouldNotAutoloadMatchingModule

    1. Kelvin Tegelaar September 29, 2020 at 3:41 pm

      Can you run “Import-module RunAsUser” and show the output of that? 🙂

      1. Sahil September 29, 2020 at 5:36 pm

        I just fixed the above issue, but now i have another one

        invoke-ascurrentuser : The ‘invoke-ascurrentuser’ command was found in the module
        ‘RunAsUser’, but the module could not be loaded. For more information, run
        ‘Import-Module RunAsUser’.
        At line:16 char:1
        + invoke-ascurrentuser -scriptblock {
        + ~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : ObjectNotFound: (invoke-ascurrentuser:String) [], Comma
        ndNotFoundException
        + FullyQualifiedErrorId : CouldNotAutoloadMatchingModule

      2. Sahil September 29, 2020 at 5:37 pm

        invoke-ascurrentuser : The ‘invoke-ascurrentuser’ command was found in the module
        ‘RunAsUser’, but the module could not be loaded. For more information, run
        ‘Import-Module RunAsUser’.
        At line:16 char:1
        + invoke-ascurrentuser -scriptblock {
        + ~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : ObjectNotFound: (invoke-ascurrentuser:String) [], Comma
        ndNotFoundException
        + FullyQualifiedErrorId : CouldNotAutoloadMatchingModule

  8. Sahil October 6, 2020 at 1:57 pm

    Hi Kelvin,
    Your Script is cool and it’s working fine.
    This Script is only working on administrator user, Script is not working on non administartor users.
    can you please fix this issue or could you please suggest here.
    How to run the script on NON Administrator forcefully

    Regards
    Sahil

    1. Kelvin Tegelaar October 6, 2020 at 8:38 pm

      From the doc:
      Sometimes you need to run an application that does not elevate itself, for this use the -NonElevatedSession switch:

      $scriptblock = { “Hello world” | out-file “C:\Temp\HelloWorld.txt” }
      invoke-ascurrentuser -NonElevatedSession -scriptblock $scriptblock

      That should solve your predicament 🙂

      1. Sahil October 14, 2020 at 7:21 am

        #Checking if ToastReboot:// protocol handler is present
        New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -erroraction silentlycontinue | out-null
        $ProtocolHandler = get-item ‘HKCR:\ToastReboot’ -erroraction ‘silentlycontinue’
        if (!$ProtocolHandler) {
        #create handler for reboot
        New-item ‘HKCR:\ToastReboot’ -force
        set-itemproperty ‘HKCR:\ToastReboot’ -name ‘(DEFAULT)’ -value ‘url:ToastReboot’ -force
        set-itemproperty ‘HKCR:\ToastReboot’ -name ‘URL Protocol’ -value ” -force
        new-itemproperty -path ‘HKCR:\ToastReboot’ -propertytype dword -name ‘EditFlags’ -value 2162688
        New-item ‘HKCR:\ToastReboot\Shell\Open\command’ -force
        set-itemproperty ‘HKCR:\ToastReboot\Shell\Open\command’ -name ‘(DEFAULT)’ -value ‘C:\Windows\System32\shutdown.exe -r -t 00’ -force
        }

        Install-Module -Name BurntToast
        Install-module -Name RunAsUser
        $scriptblock = { “Hello world” | out-file “C:\Temp\HelloWorld.txt” }
        invoke-ascurrentuser -NonElevatedSession -scriptblock $scriptblock
        {

        $heroimage = New-BTImage -Source ‘https://media.giphy.com/media/eiwIMNkeJ2cu5MI2XC/giphy.gif’ -HeroImage
        $Text1 = New-BTText -Content “Message from IT”
        $Text2 = New-BTText -Content “Your IT provider has installed updates on your computer at $(get-date). Please select if you’d like to reboot now, or snooze this message.”
        $Button = New-BTButton -Content “Snooze” -snooze -id ‘SnoozeTime’
        $Button2 = New-BTButton -Content “Reboot now” -Arguments “ToastReboot:” -ActivationType Protocol
        $5Min = New-BTSelectionBoxItem -Id 5 -Content ‘5 minutes’
        $10Min = New-BTSelectionBoxItem -Id 10 -Content ’10 minutes’
        $1Hour = New-BTSelectionBoxItem -Id 60 -Content ‘1 hour’
        $4Hour = New-BTSelectionBoxItem -Id 240 -Content ‘4 hours’
        $1Day = New-BTSelectionBoxItem -Id 1440 -Content ‘1 day’
        $Items = $5Min, $10Min, $1Hour, $4Hour, $1Day
        $SelectionBox = New-BTInput -Id ‘SnoozeTime’ -DefaultSelectionBoxItemId 10 -Items $Items
        $action = New-BTAction -Buttons $Button, $Button2 -inputs $SelectionBox
        $Binding = New-BTBinding -Children $text1, $text2 -HeroImage $heroimage
        $Visual = New-BTVisual -BindingGeneric $Binding
        $Content = New-BTContent -Visual $Visual -Actions $action
        Submit-BTNotification -Content $Content
        }

        And I want to run in simple PowerShell ise in any machine what i need to do.

  9. Kai Osthoff October 17, 2020 at 3:32 pm

    Powershell 7 & BurntToast
    It seems, that pwsh.exe is a requirement to use BurntToast with the Protocol Handler to execute scripts with a simple button.

    How do u use PWSH.EXE within Datto RMM?

    1. Kelvin Tegelaar October 17, 2020 at 8:31 pm

      The example above uses PowerShell 5. The only reason for PowerShell 7 should be for the newer functionality.

  10. Dustin October 19, 2020 at 6:51 pm

    Have you had any luck/experience implementing this through ConnectWise? Have tried a couple different implementations to no avail would love to have this available. Awesome script by the way.

  11. cory muehlebach November 5, 2020 at 7:34 pm

    I cannot figure out how to open Software center or ANY application with a button. can anyone help out?

  12. Scott Glines December 14, 2020 at 6:19 pm

    Awesome work as always Kelvin!

    In testing this on my end, underneath the hero image when the toast notification appears, it shows the PowerShell Icon, and then spells out ‘Windows PowerShell’ to the right of that icon, right above my message.

    Your gif example shows the PowerShell icon neatly placed beside your $Text1 line ‘IT Message’. Is this a quirk of later feature releases of Windows 10 from when you first posted this, or did you do something differently?

  13. Karol March 2, 2021 at 3:32 pm

    Hello! I’m very bad in scripting… and I tested a lot of scripts with notifications, and ony this is working for me…
    but I do not know, how to change buttons… I want to do some changes, example no reboot but start some exe with full permissions…
    how to change this ?

    1. Kelvin Tegelaar March 4, 2021 at 11:41 am

      You’ll have to figure that out on your own, I would strongly suggest to look at my friend and creator of the BurntToast module’s website Toastit.dev 🙂

  14. Aaron Geister March 17, 2021 at 4:53 am

    I really appreciate the scripts. I am running this in my RMM syncromsp and used import -module $env:SycroModule, and also set it to Run as Logged In user. But It times out for me on each time I test it. Any Thoughts what would give it a time out and not run?

  15. Andy March 17, 2021 at 4:10 pm

    hey man, great script. Is there a way to execute that script without having to install the Powershell Module BurntToast on every PC? So for example I just hate the module installed on a single PC and distribute the notification somehow to other PCs that don’t have the module installed. Is this possible and if yes, how do I do it?

    1. Robin May 14, 2021 at 3:33 pm

      You won’t be able to call a BurntToast function on a system without the BurntToast module installed. You’d have to write the code to create a popup yourself (you could even extract it from the BurntToast module) and pass that over to the remote system to execute.

  16. Kyle July 20, 2021 at 10:11 pm

    Cool script! It works for me.
    Is there a way to prevent them from just closing the notification without using one of the buttons provided?

  17. Kevin August 12, 2021 at 3:18 pm

    Great script. Is there a way to size the image so it isnt cutoff at the bottom? Ours seems to be “too big”
    Also @Trey you comment to move the invoke-ascurrentuser to the end of the script resolve the issue we had as well (using N-Central) so Thank You too!

Leave a comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.