Skip to main content

Yup. Another Toast Notification post 🙂 What makes this one unique is how customizable it can be with company branding and links to internal sites, interactive buttons and so on. Once you get the hang of this one you can apply this to multiple scenarios that fit your needs.

  1. Really need to read Martin Bengtsson blog before you get started. https://www.imab.dk/windows-10-toast-notification-script/ 
  2. Download the scripts here https://github.com/imabdk/Toast-Notification-Script
  3. Edit config-toast.xml to your satisfaction or desired outcome.
  4. Create a new worklet and associate to the right groups
  5. Set the Evaluation Code to whatever you want
    exit 1
  6. Set the Remediation Code Payload
    # Scheduled Task Name
    $schdtaskname = 'ToastNotify'

    # Working Directory
    $workdir = 'C:\ProgramData\ToastNotify'
    IF((Test-Path $workdir) -eq $false){mkdir $workdir -Force | Out-Null}
    IF((Test-Path $workdir\Images) -eq $false){mkdir $workdir\Images -Force | Out-Null}

    # Cleanup Prior Scheduled tasks if they exist
    Unregister-ScheduledTask -TaskName "$schdtaskname" -Confirm:$false | Out-Null

    # Build RunToastHidden.cmd
    $cmd = @"
    powershell.exe -NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -File "$workdir\New-ToastNotification.ps1"
    "@
    New-Item -Path "$workdir" -Name "RunToastHidden.cmd" -ItemType "file" -Value $cmd -force | Out-Null

    # Copy Toast Notify Files
    $collection = 'ToastLogoImageDefault.jpg','ToastHeroImageDefault.jpg'
    foreach ($file in $collection){
    IF(!(Test-Path $workdir\$file)){ Copy-Item ".\$file" $workdir\Images | out-null }
    }
    $collection = 'New-ToastNotification.ps1','config-toast.xml','Hidden.vbs'
    foreach ($file in $collection){
    IF((Get-ChildItem $workdir\$file -ErrorAction SilentlyContinue).LastWriteTime -lt (Get-ChildItem $file).LastWriteTime){
    Copy-Item ".\$file" $workdir | out-null
    }
    }

    # Scheduled Task Functions
    Function Schedule-ToastNotify{
    $TaskStartTime = (Get-Date 12pm)
    $SchedService = New-Object -ComObject Schedule.Service
    $SchedService.Connect()
    $Task = $SchedService.NewTask(0)
    $Task.RegistrationInfo.Description = 'ToastNotify'
    $Task.Settings.Enabled = $true
    $Task.Settings.AllowDemandStart = $true
    $Task.Settings.WakeToRun = $true
    $trigger = $Task.triggers.Create(1) # https://docs.microsoft.com/en-us/windows/win32/taskschd/triggercollection-create
    $trigger.StartBoundary = $TaskStartTime.ToString("yyyy-MM-dd'T'HH:mm:ss")
    $trigger.Enabled = $true
    $action = $Task.Actions.Create(0)
    $action.Path = "wscript"
    $action.Arguments = "`"$workdir\Hidden.vbs`" `"$workdir\RunToastHidden.cmd`""
    $taskFolder = $SchedService.GetFolder('\')
    $taskFolder.RegisterTaskDefinition("$schdtaskname", $Task , 6, 'Users', $null, 4) | out-null
    }

    # Running Scheduled Task Function
    function Run-ToastNotify{
    IF($TaskSchd -eq $true){
    $time = New-ScheduledTaskTrigger -At (Get-Date).AddSeconds(3) -Once
    Set-ScheduledTask -TaskName "$schdtaskname" -Trigger $time | Out-Null
    }
    }

    # Schedule and Run the Scheduled Task
    Schedule-ToastNotify
    Run-ToastNotify
  7. Upload ALL the files to the worklet (config-toast.xml, Hidden.vbs, New-ToastNotification.ps1, ToastHeroImageDefault.jpg, ToastLogoImageDefault.jpg):
  8. Run worklet

 

After the worklet runs the end user should see this. Take note of the notification icon in lower right with the “moon” logo indicating that Focus Assist is on.
 

 

 

Here you can see where the worklet put data on the local drive

 

Troubleshooting

log data for the toast notification is stored under C:\Users\%username%\AppData\Roaming\ToastNotficationScript\

 

 

Hi,

We are currently looking for something like this, but I can´t get this worklet to run.

Through Powershell, on my machine, it works, but through Automox it´s not working, and we can´t debug why.

Thanks


Sometimes I will drop in this line at the top of the script to get an idea of what is happening when the worklet runs. After the worklet runs I review output of C:\windows\temp\worklet.txt

 

Start-Transcript C:\Windows\Temp\worklet.txt

Another option is try and see what is not happening by evaluating the various parts the script accomplishes

  1. Does it create the directory C:\ProgramData\ToastNotify
  2. Does it create a file C:\ProgramData\ToastNotify\RunToastHidden.cmd
  3. Did these files “config-toast.xml, Hidden.vbs, New-ToastNotification.ps1, ToastHeroImageDefault.jpg, ToastLogoImageDefault.jpg” get copied under C:\ProgramData\ToastNotify
  4. Does a schedule task called ToastNotify exist
  5. Is there a history of the Scheduled Task running
  6. If the scheduled task is running, did any output get generated here C:\Users\%username%\AppData\Roaming\ToastNotficationScript\ for the currently logged in user...

Thanks for the ideas! 

We’ll keep debugging and update here with our final output. 


Hi Jack, after suggesting using this for reboots on another thread, I’ve been pondering what else to use it for.

 

The obvious one seems to be when you need a user to close an application before running an update. Looking at the Toast script, you can add functionality to invoke an install from SCCM, but would there be a way to trigger an Automox policy to run? Do you know if the locally installed agent for example could be triggered to run a particular policy?

Thanks!

Danny


I’d like to think it would be possible. To run an Automox policy one would need to know the Client ID and the Policy ID.

 

Here is sample code that would let you run a policy from Automox remotely. Assumes a few things:

  • assets.csv is a payload containing every asset under columns: hostname,id (otherwise during runtime you would have to query all assets just to filter on the asset or have the id pre-staged on each client)
  • PolicyID which you is in the URL of any policy you find in the console. 
$assetid = (Import-Csv assets.csv | where-object hostname -eq $env:COMPUTERNAME).id
$policy = '123456'
$orgID = '12345'

$headers = @{ "Authorization" = "Bearer $apiKey" } # $apiKey being stored as secret in Automox

$url = "https://console.automox.com/api/policies/$policy/action?o=$orgID&action=remediateServer&serverId=$AssetId"
Invoke-WebRequest -Method POST -Uri $url -Headers $headers -UseBasicParsing

The next part, I’m going to assume will require a lot of trial and error to get just right. 

In the script “New-ToastNotification.ps1” around line 1998, you might be able to just replace the code under the section “Running RunApplicationID function” with the code block above instead of having it call that function “Write-ApplicationIDRegistry”. I’m about 90% sure that would work. I’ve not tried it, but would think it possible. 

Ultimately to solution this it requires time and a deep understanding of how this XML configuration file and “New-ToastNotification.ps1” script runs. Paying close attention to those action buttons and how that flow works. 

 

 


On second thought… perhaps just code in the remediation necessary over trying to figure out Policy and Agent IDs. I mean, at this point a script is running to display all the content to the end user. 


Hi Jack, thanks for this, I’ll see what I can do and will report back.

 

So just put the remediation script in “New-ToastNotification.ps1” rather than calling back to Automox (which I guess will report on success insomuch as the application will show as installed?)?

 

Of course, any third or fourth thoughts you have along the way would be greatly received as I’m flying by the seat of my pants!


The actions run during the New-ToastNotification.ps1 script runtime would not reflect in the Automox worklet activity log. Only exit codes from the code dropped in the remediation section of the worklet reflect in the activity log. Which makes a feedback loop for anything executed beyond that a bit harder.  

 

To get a desired feedback I’d focus on the evaluation code in the worklet itself, which in a way can help us see successful or not in the console. Given you may be waiting for user input, the immediate evaluation run post remediation code is not likely to yield anything great. Will have to rely on the scan cadence (default is 24 hours) or manually do a re-scan of the agent. 

You definitely have a pretty cool use case here. re-purposing this tool to act in a similar way  as it already does with SCCM would likely close a lot of gaps I think all of us Automox customers would love to have. Complex workflows with on-demand end user interactions.

 

 


So, here’s my plan… it’ll end up with multiple policies but I’m ok with that!

Policy 1. - Uses your script to create the schedule task (I have it set to trigger 15 mins after it’s installed)

Policy 2. - Installs the relevant software

 

So Policy 1 kicks off the toast notification, which in turn runs the code you posted 4 posts above to run Policy 2 which does the standard eval and remediation - this way it all reports correctly, but the user has been able to close down their apps before installing.

 

Last thing standing in my way… Getting Toast to run a custom protocol and script to kick off Policy 2… anyone played with this and got it to do something bespoke?


Dig the strategy play here @IamDannyOConnor.

 

I’m thinking outload here without looking. I’m almost wondering is there a way to take advantage of the do while loop in PowerShell to achieve that desired outcome.

For example, a do-while loop that just waits for a file to show up with a built in timeout. That way you can say wait for this file to show up that indicates a user has interacted with the prompt, to now execute the code. Which could build a way between the Automox worklet and balloon tip pop-up, the script now 5 posts above 🙂, to play well together and avoid having two policies. 

 


Oooooo, like your thinking!

 

 


K, the do-while loop works perfectly, as soon as I (manually) drop the test text file in it does it’s thang!

 

Just to get the Toast ‘Action’ button to instigate the file creation now, it’s being a bit stubborn for some reason.

 

 


Think I’ve cracked it. As soon as I’ve tided up the code I’ll start a new thread  as I think it deserves it 🤣


when testing (or in general), modify the first line of the Schedule-ToastNotify function to set the time to just a few minutes from now…. If you leave it as suggested (12pm today), that may have passed resulting in never running the script or may be far off making testing tricky.  Changing to the line below would run the task 2 minutes from when the worklet is doing its thing, which should be plenty of time… keep in mind that it will take an additional minute or so after this 2 minute delay til it pops up.


$TaskStartTime = iDateTime]::Now.AddMinutes(2)


Does anyone know if there is a way to set the maximum number of deferrals?  it seems like its infinity to me.  


Reply