I made a powershell code that shows a toast-message with a "yes/no" option. Now I am looking for a way to handle that click-event properly. See below the code I have so far:
cls
Remove-Variable * -ea 0
$ErrorActionPreference = 'stop'
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]
# now lets define a toast-message:
$toastXml = [Windows.Data.Xml.Dom.XmlDocument]::new()
$xmlString = @"
<toast launch = "Test1" scenario='alarm'>
<visual>
<binding template="ToastGeneric">
<text>Title</text>
<text>Message</text>
</binding>
</visual>
<audio src="ms-winsoundevent:Notification.Looping.Alarm" />
<actions>
<action content="yes" arguments="yes" />
<action content="no" arguments="no" />
</actions>
</toast>
"@
$toastXml.LoadXml($XmlString)
$toast = [Windows.UI.Notifications.ToastNotification]::new($toastXml)
$appId = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
$notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId)
$notify.Show($toast)
Here I need a way to wait for the event, when the users clicks the "yes/no" buttons in the toast (or the "x" to close all). It should work without an external DLL, but using e.g. "add_Activated" and an eventHandler like in this c# code: https://github.com/david-risney/PoshWinRT/blob/master/PoshWinRT/EventWrapper.cs
I thought it should be similar like for the other cs-Wrapper from that PoshWinRT project, which I could finally convert into this:
# replacement for PoshWinRT - AsyncOperationWrapper:
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$asTaskGeneric = foreach($method in [System.WindowsRuntimeSystemExtensions].GetMethods()) {
if ($method.name -ne 'AsTask') {continue}
if ($method.GetParameters().Count -ne 1) {continue}
if ($method.GetParameters().ParameterType.Name -ne 'IAsyncOperation`1') {continue}
$method
break
}
function Await($WinRtTask, $ResultType) {
$asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
$netTask = $asTask.Invoke($null, @($WinRtTask))
$null = $netTask.Wait(-1)
$netTask.Result
}
# sample for async operation:
$null = [Windows.Storage.StorageFile, Windows.Storage, ContentType = WindowsRuntime]
$file = 'c:\windows\notepad.exe'
$info = await ([Windows.Storage.StorageFile]::GetFileFromPathAsync($file)) ([Windows.Storage.StorageFile])
$info
Unfortunately, the coding for that eventWrapper seesm to be a bit more tricky. These are the code-snippets that I could not bring to life, but they may point into the right direction:
$method = [Windows.UI.Notifications.ToastNotification].GetMethod('add_Activated')
$handler = [Windows.Foundation.TypedEventHandler[Windows.UI.Notifications.ToastNotification,System.Object]]::new($toast, $intPtr1)
$handler = [System.EventHandler]::new($toast, $intPtr2)
Especially the second IntPtr-parameter required for creating an eventHandler confuses me totally.
Any input is more than welcome. Many thanks.
After spending some days of investigations and testing the different options I am now happy to finally share a Powershell code-snippet which is able to react on any button being pressed in a Toast-GUI. Here the code...
Param($data)
cls
$ErrorActionPreference = 'stop'
# here we handle any input-parameter coming from the below Toast-Action:
if ($data) {
# (optional) show the value of the parameter for 5 seconds:
$objShell = New-Object -ComObject "WScript.Shell"
$objShell.Popup("Parameter from Toast-GUI: $data", 5)
# insert your own code here...
}
# make sure not to run into a loop:
if ($data) {break}
# define global variables:
$temp = $env:Temp
$launchFile = "$temp\ToastAction.exe"
# make sure we re-create a new launchFile:
if (Test-Path $launchFile -PathType Leaf){Remove-Item $launchFile -Force}
if (Test-Path $launchFile -PathType Leaf){break}
# get the full path of this script:
$script = $myInvocation.MyCommand.Definition
if (!$script) {$script = $psISE.CurrentFile.Fullpath}
# create the VBS-file as a hidden launcher for Powershell:
$vbs = @"
set objWsh = CreateObject("WScript.Shell")
powershell = objWsh.ExpandEnvironmentStrings("%windir%\system32\WindowsPowershell\v1.0\powershell.exe")
set objFs = CreateObject("Scripting.FileSystemObject")
if WScript.Arguments.Count > 0 Then
arg = " """ + wscript.arguments(0) + """"
objWsh.Run powershell + " -NoP -NonI -W Hidden -Exec Bypass -File ""$script""" + arg, 0
wscript.sleep 1000
end if
"@
$vbs | out-file -LiteralPath "$temp\start.vbs" -Encoding ascii -Force
# create an IEXPRESS SED-file:
$SedFile = [System.IO.Path]::GetTempFileName()
$body = @"
[Version]
Class=IEXPRESS
SEDVersion=3
[Options]
ShowInstallProgramWindow=0
HideExtractAnimation=1
UseLongFileName=1
RebootMode=N
TargetName=$launchFile
FriendlyName=ToastAction
AppLaunched=wscript.exe /b
PostInstallCmd=<None>
SourceFiles=SourceFiles
[Strings]
FILE0="start.vbs"
[SourceFiles]
SourceFiles0=$temp\
[SourceFiles0]
%FILE0%=
"@
$body | out-file -LiteralPath $SedFile -Encoding ascii -Force
# create an executable via IEXPRESS as a target for our custom protocol:
$fso = New-Object -ComObject Scripting.FileSystemObject
$shortName = $fso.getfile($SedFile).ShortPath
start-process -FilePath "$env:windir\SYstem32\iexpress.exe" -ArgumentList "/N $shortName /Q" -Wait
# cleanup temporary files:
Remove-Item -LiteralPath $SedFile
Remove-Item -LiteralPath "$temp\start.vbs"
# register launchFile as a handler for the custom protocol:
$protocolName = "ToastAction"
$regPath = "HKCU:\Software\Classes\$protocolName"
$fullName = [System.IO.FileInfo]::new($launchfile).FullName
New-Item "$regPath\shell\open\command" -Force -ErrorAction SilentlyContinue | Out-Null
New-ItemProperty -LiteralPath $regPath -Name 'URL Protocol' -Value '' -PropertyType String -Force -ErrorAction SilentlyContinue | Out-Null
New-ItemProperty -LiteralPath $regPath -Name '(default)' -Value "URL:$($protocolName)" -PropertyType String -Force -ErrorAction SilentlyContinue | Out-Null
New-ItemProperty -LiteralPath "$regPath\shell\open\command" -Name '(default)' -Value """$fullname"" /C:""wscript start.vbs """"%1""""""" -PropertyType String -Force -ErrorAction SilentlyContinue | Out-Null
Add-Type -AssemblyName System.Runtime.WindowsRuntime
$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime]
# now lets define a toast-message:
$toastXml = [Windows.Data.Xml.Dom.XmlDocument]::new()
# here comes the toast-message using the custom protocol:
$xmlString = @"
<toast scenario="reminder">
<visual>
<binding template="ToastGeneric">
<image placement="hero" src="file:///C:\Windows\Web\Wallpaper\Spotlight\img50.jpg"/>
<image id="1" placement="appLogoOverride" hint-crop="circle" src="file:///C:\Windows\System32\WindowsSecurityIcon.png"/>
<text>Good evening</text>
<text placement="attribution">www.mycompany.com</text>
<group>
<subgroup>
<text hint-style="title" hint-wrap="true">Make your choice!</text>
</subgroup>
</group>
<group>
<subgroup>
<text hint-style="body" hint-wrap="true">Take the blue Pill and remain a slave to the illusion. </text>
</subgroup>
</group>
<group>
<subgroup>
<text hint-style="body" hint-wrap="true">Take the red Pill and become a slave to reality.</text>
</subgroup>
</group>
</binding>
</visual>
<actions>
<action activationType="protocol" arguments="ToastAction://bluePill?value=123" content="Blue Pill"/>
<action activationType="protocol" arguments="ToastAction://redPill?value=456" content="Red Pill"/>
<action activationType="system" arguments="dismiss" content="Dismiss"/>
</actions>
</toast>
"@
# (optional) set custom Toast app-Id:
$appId = 'Toast.MyCompany.App'
$AppDisplayName = 'My User Notification'
$IconUri = "C:\Windows\System32\InputSystemToastIcon.png"
$RegPath = "HKCU:\Software\Classes\AppUserModelId\$appId"
if (!(Test-Path $RegPath)) {$null = New-Item -Path $AppRegPath -Name $appId -Force}
$null = New-ItemProperty -Path $RegPath -Name DisplayName -Value 'My User Notification' -PropertyType String -Force
$null = New-ItemProperty -Path $RegPath -Name ShowInSettings -Value 0 -PropertyType DWORD -Force
$null = New-ItemProperty -Path $RegPath -Name IconUri -Value $IconUri -PropertyType ExpandString -Force
$null = New-ItemProperty -Path $RegPath -Name IconBackgroundColor -Value 0 -PropertyType ExpandString -Force
# create/launch the Toast-GUI:
$toastXml.LoadXml($XmlString)
$toast = [Windows.UI.Notifications.ToastNotification]::new($toastXml)
$toast.ExpirationTime = [datetime]::Now.AddSeconds(30)
$notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId)
$notify.Show($toast)
A custom URL protocol can only trigger a batch-file or an executable. And because the batch file will always result into a CMD-window popping up, I decided to create a VBS-file as a script-launcher and I do "compile" that VBS-file via windows-integrated tool "iexpress" into a standalone executable.
It sounds more complex than it is at the end and finally that was the solution I was always looking for ;-)