How to notify logged-on users

You may have already seen or know how to display a nice pop-up toast notification to your Windows 10 users.
If not, I’d like to point out some material related to these notifications:

Now, that you’re familiar with the notifications part, let’s see how to display them.
Being a big fan of what’s robust and sustainable, I’ll show you how to leverage the built-in Windows 10 scheduled tasks to notify your users 😀

Let’s say you have a PowerShell script that triggers a notification in the end-user context. It’s named toast.ps1.
Let’s say you’ve a Group Policy (GPO) that runs a scheduled task under the NT AUTHORITY\System account.

How can you escape the SYSTEM account and run the Toast.ps1 in every interactively logged on standard account?

Well, the solution is to identify these interactively logged on users and run a new scheduled task that targets their identity.

# Identify interactive users
try {
 $Users = @(Get-WmiObject -Query 'Select *  from Win32_Process' -ErrorAction Stop) |
 Where-Object { $_.ProcessName -eq 'explorer.exe'} |
 ForEach-Object {
   User = '{0}\{1}' -f $($_.GetOwner().Domain),$($_.GetOwner().User)
} catch {
 Write-Warning -Message "Failed to get logged on users because $($_.Exception.Message)"
# If any and hour is 10AM for example, notify
if ($Users -and ((Get-Date).ToString('HH',[Globalization.CultureInfo]::InvariantCulture) -in '10')) {
 $Users | ForEach-Object {
 Start-UserNotification -UserName $_ -Prefix 'WhatEver' -FilePath 'C:\Toast.ps1'

Here’s the code sample of the Start-UserNotification function used above:

#Requires -RunAsAdministrator
Function Start-UserNotification {
Start a script in the user context
Start a script in the user context using scheduled tasks
String that represents the targeted user name
String that represents the path to the script to be executed
String that is being used to prefix the task name
Start-UserNotification -UserName LocalUserName -FilePath C:\notif.ps1
[ValidateScript({Test-Path -Path $_ -PathType Leaf})]
Begin {
$errHT = @{ ErrorAction = 'Stop' }
$cmd = '& {0}' -f [Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($FilePath)
$aHT = @{
Execute = 'C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe'
Argument = '-NoProfile -WindowStyle Hidden -Exec Bypass -Command "{0}"' -f "$($cmd)"
Process {
End {
# Notify user using a Scheduled task
try {
$taskName = '{0}-Notify' -f [regex]::Escape($Prefix)
Get-ScheduledTask -TaskPath '\' @errHT | Where-Object { $_.TaskName -match "^$($taskName)_" } | ForEach-Object {
if ($pscmdlet.ShouldProcess("$($_.TaskName)", 'Remove previous task')) {
$null = $_ | Stop-ScheduledTask -Verbose @errHT
$null = $_ | Unregister-ScheduledTask -Verbose -Confirm:$false @errHT
Write-Verbose -Message "Successfully remove task $($_.TaskName)"
} catch {
Write-Warning -Message "Failed to stop and unregister because $($_.Exception.Message)"
try {
$HT = @{
TaskName = '{0}_{1}' -f "$($taskName)","$($UserName)"
User = '{0}' -f "$($UserName)"
Force = [switch]::Present
Action = (New-ScheduledTaskAction @aHT @errHT)
Settings = (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility 'Win8' @errHT)
if ($pscmdlet.ShouldProcess("$($HT['TaskName'])", 'Create and run task')) {
$null = Register-ScheduledTask @HT @errHT | Start-ScheduledTask @errHT
Write-Verbose -Message "Successfully created and ran task $($HT['TaskName'])"
} catch {
Write-Warning -Message "Failed to register and run notification task because $($_.Exception.Message)"

Here’s how to use the function:

End of Life of Flash?

  • Context

Since Adobe will no longer be supporting Flash Player after December 31, 2020 and Adobe will block Flash content from running in Flash Player beginning January 12, 2021, Adobe strongly recommends all users immediately uninstall Flash Player to help protect their systems.


This statement is worrying because it suggests that Adobe has some sort of kill switch.

It appears that they have hardcoded the EoL (End-Of-Life) date in the code. If you change the date of your computer before January 12, 2021, you’ll notice a different behavior and won’t get the above blue EoL logo.

Instead you can get for example, just a reminder (IE11 in my case):

  • Issue

You may still have a critical Line of Business (LoB) application that requires Flash to run in a browser.

What can you do after the EoL date?

If you don’t rely on Flash at all, instead of waiting for it to be removed by Microsoft, you can for example implement what I described in this post to avoid Flash from being instantiated at all in Windows and Office. (This post doesn’t include what you’d need to do for Google Chrome or Edge Chromium).

  • Solution

Let’s you have to run Flash for a critical LoB application after January 12, 2021. What can be done?

Well, it depends on the browser.

Let’s say you want it to run in Google Chrome or Edge Chromium.

Both Chromium based browsers (Edge and Google) implement the Chromium based Flash component named PepperFlash.

First, you’ll need to get rid of what’s installed under:

$($env:LOCALAPPDATA)\Google\Chrome\User Data\PepperFlash
$($env:LOCALAPPDATA)\Microsoft\Edge\User Data\PepperFlash

Yes, it’s a per-user non-roaming location 😦
Once, you did the above step, you don’t have any Flash at all in these Chromium based browsers.

To be able to restore Flash, you need to get an older vulnerable version of the PPAPI provided by Adobe and install it.
Once installed, you’ll have for example a pepflashplayer64_32_0_0_371.dll file in C:\Windows\system32\Macromed\Flash.
Don’t ask me what I think about having 2 vulnerable dll files planted in System32 🙄 .

NB: The following registry keys store the version and location of the Adobe PPAPI component: HKLM\SOFTWARE\Macromedia\FlashPlayerPepper and HKLM\SOFTWARE\WOW6432Node\Macromedia\FlashPlayerPepper

To make Google Chrome or Edge Chromium load it, you’ll have to create a folder in these per-user locations

mkdir "$($env:LOCALAPPDATA)\Google\Chrome\User Data\Default\Pepper Data\Shockwave Flash\System"
mkdir "$($env:LOCALAPPDATA)\Microsoft\Edge\User Data\Default\Pepper Data\Shockwave Flash\System"

You’ll also need to create files there, named mms.cfg, that contain some instructions:

'System32','SysWOW64' |
Foreach-Object {
 $p = $_
 try {
'@ | 
  Out-File -FilePath "C:\Windows\$($p)\Macromed\Flash\mms.cfg" -Encoding UTF8 -Force -ErrorAction Stop
 } catch {
  Write-Warning -Message "Failed to write C:\Windows\$($p)\Macromed\Flash\mms.cfg because $($_.Exception.Message)"

NB 1: Yes, I’ve created the mms.cfg in C:\Windows\System32\Macromed\Flash.
It just needs to be copied to the “LOCALAPPDATA..\Shockwave Flash\System” folders created previously.

NB 2:
The Adobe® Flash® Player 32.0 Administration Guide (uploaded here as well, if it disappears any time soon) says the following:

EnableAllowList. Disabled by default.
Allows system administrators to allow Flash Player to only load content from a set of allowed URLs.
After Flash Player EOL, EnableAllowList will default to true and the MMS.CFG setting will be ignored.

Since you rolled out an older version, you need this setting to be enabled 🙂 .

You may also want to have a tighter control on Flash in these Chromium based browsers using Group Policy that block or allow an list of URL if you don’t trust that much text files (mms.cfg, especially in the user profile as it can be modified).
Here are 2 examples:

Let’s say you want Flash to run in Internet Explorer 11.
You’ve already created the 2 mms.cfg files in C:\Windows\System32\Macromed\Flash and C:\Windows\SysWOW64\Macromed\Flash.

There are two security updates that would need to be removed:
4580325, Security Update for Adobe Flash Player: October 13, 2020
4561600, Security Update for Adobe Flash Player: June 9, 2020
Yes, that’s worse! Now, all binaries under C:\Windows\System32\Macromed\Flash are vulnerable and can potentially be loaded in a browser 😥