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!

26 thoughts on “Monitoring with PowerShell: Notifying users of Windows Updates

  1. Justin

    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

    Reply
    1. Kelvin Tegelaar Post author

      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.

      Reply
      1. Justin

        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”

        Reply
        1. luis

          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

          Reply
          1. Kelvin Tegelaar Post author

            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

    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.

    Reply
  3. Tom

    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

    Reply
    1. Kelvin Tegelaar Post author

      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. 🙂

      Reply
  4. Patrick

    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?

    Reply
      1. Patrick

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

        Reply
  5. Shaun

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

    Thanks

    Reply
  6. Travis Phipps

    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?

    Reply
    1. Kelvin Tegelaar Post author

      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!

      Reply
  7. Sahil

    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

    Reply
      1. Sahil

        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

        Reply
      2. Sahil

        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

        Reply
  8. Sahil

    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

    Reply
    1. Kelvin Tegelaar Post author

      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 🙂

      Reply
      1. Sahil

        #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.

        Reply
  9. Kai Osthoff

    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?

    Reply
  10. Dustin

    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.

    Reply

Leave a Reply

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.