Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Ask questions about creating Graphical User Interfaces (GUI) in PowerShell and using WinForms controls.
Forum rules
Do not post any licensing information in this forum.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
Locked
User avatar
zztemp
Posts: 24
Joined: Mon Apr 03, 2017 6:58 am

Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by zztemp »

Product, version and build: latest patch/version
Operating system: 64 bit windows enterprise

Hi,

I'm creating a GUI that sits in the systemtray. When it is clicked, it shows you computer information.
I have "drawn" the XAML with visual studio and i use Powershell Studio to make it into an EXE.
This all works fast and just like i want it.

Now the tool is meant to serve a second purpose, being an emergency communication platform with toast notifications.
A second script i wrote, copies a JSON file to a location on the computers harddrive, at the moment for testing this is just c:\temp\json.json
The GUI has a function called ShowMessage. This uses the build-in toast notification system provided by MS.

The function goes as follows. (some words are in Dutch)
  1. $global:file = "C:\Temp\json.json"
  2.  
  3.     # start listening
  4.  
  5.     FUNCTION global:ShowMessage
  6.  
  7.     {
  8.  
  9.         if (Test-Path -Path $file)
  10.  
  11.         {  
  12.  
  13.             $script:file = "C:\Temp\json.json"
  14.  
  15.             $message = Get-Content $file | convertfrom-json
  16.  
  17.             # if body text is too long, trim and add 3 dots
  18.  
  19.             if ($message.Body.Length -gt 115)
  20.  
  21.             {
  22.  
  23.                 $message.body = $message.Body.Substring(0, 115) + "..."
  24.  
  25.             }
  26.  
  27.            
  28.  
  29.             $app = 'Microsoft.Windows.Computer'
  30.  
  31.             #$app = 'windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel'
  32.  
  33.             [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
  34.  
  35.            
  36.  
  37.             $Template = [Windows.UI.Notifications.ToastTemplateType]::ToastImageAndText01
  38.  
  39.            
  40.  
  41.             #Gets the Template XML so we can manipulate the values
  42.  
  43.             [xml]$ToastTemplate = ([Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent($Template).GetXml())
  44.  
  45.            
  46.  
  47.             switch ($message.Status)
  48.  
  49.             {
  50.  
  51.                 'beschikbaar'   { $image = "$($env:TEMP)\beschikbaar.png" }
  52.  
  53.                 'ONBESCHIKBAAR' { $image = "$($env:TEMP)\onbeschikbaar.png" }
  54.  
  55.                 'storingen'     { $image = "$($env:TEMP)\storingen.png" }
  56.  
  57.             }
  58.  
  59.            
  60.  
  61.             write-host 'Activated showmessage'
  62.  
  63.             ''
  64.  
  65.             write-host $($message.Title)
  66.  
  67.             write-host $($message.Status)
  68.  
  69.             write-host $($message.Body)
  70.  
  71.             ''
  72.  
  73.             [xml]$ToastTemplate = @"
  74.  
  75.                             <toast launch="app-defined-string" >
  76.  
  77.                               <visual>
  78.  
  79.                                 <binding template="ToastGeneric">
  80.  
  81.                                   <text>$($message.Title) - $($message.Status) </text>
  82.  
  83.                                   <text>$($message.Body)</text>
  84.  
  85.                                   <text>$($message.Date)</text>                          
  86.  
  87.                                   <image placement="appLogoOverride" src="$($image)"/>
  88.  
  89.                                   <text placement="attribution">$($message.Label)</text>                            
  90.  
  91.                                   </binding>
  92.  
  93.                               </visual>
  94.  
  95.                             </toast>
  96.  
  97.     "@
  98.  
  99.             $ToastXml = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument
  100.  
  101.             [void]$ToastXml.LoadXml($ToastTemplate.OuterXml)
  102.  
  103.             $notify = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($app).Show($ToastXml)
  104.  
  105.             Remove-Item $file -Force -Confirm:$false
  106.  
  107.         } # END IF TEST PATH
  108.  
  109.     } # END FUNCTION
  110.  
  111.    
  112.  
  113.     $timer = New-Object Timers.Timer
  114.  
  115.     $timer.Interval = 10000 # fire every 10s
  116.  
  117.     $timer.Enabled = $true
  118.  
  119.     $timer.AutoReset = $true
  120.  
  121.     $timer.start()
  122.  
  123.  
  124.  
  125.  
  126.  
  127.     Get-EventSubscriber -SourceIdentifier ShowMessage | Unregister-Event
  128.  
  129.     Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier showmessage -Action {showmessage}
On its own this function works perfectly. Now is i run the following code in my EXE or even just within Powershell Studio, this doesn't work.

As far as i can tell, this is due to runspaces. But i can't seem to figure out how to make this function 'act\\run' in the same runspace as the rest of my code, being the GUI.

So to further clarify, if i run the code above in ISE and i just place the JSON-file at the location C:\\temp, this works perfectly, time and time again. It's just not working within Powershell Studio and in the build EXE.

I hope i made my post clear enough as to what the problem is.

User avatar
Alexander Riedel
Posts: 7457
Joined: Tue May 29, 2007 4:43 pm

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by Alexander Riedel »

It's a problem of who is charge of the process. If you take the code you posted and put it in a Powershell console or the ISE (which really is just another console), it works because the process persists after your script ends. That means the runspace exists as well and your code is still there, the timer kicks in and calls your function. So all works as intended.
This makes sense for a console, since it would be silly to terminate the process after each command issued.

Developing scripts in PowerShell Studio as well as when packaging your script into an exe puts YOU in charge of the process. To make a long story short, the process ends when your script ends.
You scripts (normally) do not run in a console but in their own process. If you package a script as an exe it must be able to be executed outside of a console, so you cannot rely on the process to exist after your script ends. Your script determines the lifetime of the process.

If you look at the code here: https://www.sapien.com/blog/2017/07/10/ ... owershell/
you see it contains a loop that keeps the process alive until you as the developer or the user decides to exit.
Alexander Riedel
SAPIEN Technologies, Inc.

User avatar
zztemp
Posts: 24
Joined: Mon Apr 03, 2017 6:58 am

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by zztemp »

Hi Alexander,

Thanks for responding. I read through that post and i did a test (well, quite a few). I tried to make an EXE with just the ShowMessage() and Register-ObjectEvent. This indeed works thanks to that while that keeps the scrips running and active.

I then tried to combine it with my full XAML GUI script. The GUI stayed responsive, altho i now have 2 systemtray icons. One that i create by code and one created by PS Studio. But that is not that big of a deal. The problem is that this probably creates two environment in which it runs? I'm guessing here.
The reason for that is, i use the following two lines at the very end of my script, without them, it won't work.

Code: Select all

$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
I used to Build the script as Windows Forms, not as Windows SystemTray app.

My XAML starts with the following:

Code: Select all

<Controls:MetroWindow
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
        Title="PC Informatie"
		Height="439.748" Width="450"
		UseNoneWindowStyle="true"  ResizeMode="NoResize"  ShowInTaskbar="False"  AllowsTransparency="True" WindowStyle="None" Background="Transparent"
    
So it uses MahApps and is a MetroWindow. I add it because maybe this has an influence?

Any further suggestions?

User avatar
Alexander Riedel
Posts: 7457
Joined: Tue May 29, 2007 4:43 pm

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by Alexander Riedel »

I have moved this thread to the PowerShell GUI section. I am hoping James Vierra has some additional input on this.
Alexander Riedel
SAPIEN Technologies, Inc.

jvierra
Posts: 14543
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by jvierra »

Here is an example of how to run a "Toaster" with a form. THe example uses a Windows Form but the same method will work with a WPF form.
Attachments
Demo-ToastMessages.psf
(54.32 KiB) Downloaded 121 times

User avatar
zztemp
Posts: 24
Joined: Mon Apr 03, 2017 6:58 am

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by zztemp »

Hi Jvierra,

Thanks for taking the time to create this. I've downloaded it and tested. This gives me the same result as "my way" did but i am now going to try and implement your technique into my full systemtray-GUI. Afterwards i'll report back.

jvierra
Posts: 14543
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by jvierra »

Remember runspaces are isolated. You cannot directly communicate between them.

Without some example of what you are trying to do it is not really possible to know what your issue is.

User avatar
zztemp
Posts: 24
Joined: Mon Apr 03, 2017 6:58 am

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by zztemp »

Hi,

So i implemented your code into mine and it worked within five minutes, so again thanks !!
The reason why i didn't post my full code is simply because all the images that it holds, are made with encoding and decoding To and From Base64String.
So i would have to replace them by something that's not representing the company i work for.

Now what i might do tomorrow is replace them with a relative path and make sure the resources are on the client in the hopes that memory usage drops down a bit. If i have the time i might change the images with placeholders and i could share the full script.

Anyway, i like to use this opportunity to learn a bit more about these runspaces. I'm not a full-time developer nor do i want to become one but i do script quite often so this might be something useful a long the way since this is a vague concept/subject to me.

I did quite an extensive search before posting over here (and Reddit).
How could i have known this technique? What Google searches would have led me to it? I think i came across this technique during my search but it didn't "click".

My assumption on "it's runspaces" was correct. I have build my code not as a systemtray app but as a windows form.
Am i correct in assuming that this creates one runspace and you create one for powershell within that exe (and trigger my code within that runspace) ?
So they are separate but build into one and thus running at the same time? Jeez i hope this makes sense :/

jvierra
Posts: 14543
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by jvierra »

Hi ZZ,

Runspaces are specific to PowerShell. The are the PS model of threads/tasks for Net. There are many blogs about runspaces but most are just copies of other posts. Most bloggers are not programmers outside of scripting and have yet to learn how to dig into the OS and how Net supports the concept and API of threading. The concept of "async" is also not well understood.

The PowerShell DK contains examples of how to use runspaces and the runspace factory. Examples are C# but can be adapted to PowerShell.

Start by reviewing the runspace documentation and ask yourself why each object, method, property and event are part of the class. You will not be able to answer this question most of the time but it will help you when trying to design a solution. Your "need" for a solution will, often, remind you of what you have read and questioned.

Overall understanding only comes with technical programming experience. Over time you will begin to understand how APIs are designed and realize that certain capabilities are to be expected. The task then is to discover how the API designers implemented those capabilities.

Suggestion. If you want this capability available at all time why not just create a service that does the message display. There is really no need for the code to exist in the form script.

User avatar
zztemp
Posts: 24
Joined: Mon Apr 03, 2017 6:58 am

Re: Register-ObjectEvent in combination with GUI\XAML and Powershell Studio

Post by zztemp »

I had a brief look at the SDK for powershell 3.0 (there does not seem to be one for 5.0).
As you correctly pointed out, the examples are in C# and this is a bit of an issue for me.
Yes i understand what the code does, but because of me not knowing any of the syntax, i'm not possible to translate it into PS and do some further experimentation with it.
This has been an issue in the past for me where i find C# examples but can't translate them into PS. This has made me wonder if i should learn some basic C# (not much further than the Hello-World, but that i know how the syntax works at the very least.

I found a 2-part video on YT where Runspaces are shown and explained in Powershell. It even shows how you can communicate between the two.

I'm going to experiment a bit more with these runspaces because i see the great value they bring to responsive GUI creation.

As for your question why i don't make it into a service: Well than i would need to create two executables that i need to deploy. One for the systemtray app and one for the service. So the deployment just became bigger. Then i'd have to create a service for it on each computer by GPO, even more overhead.
With having everything in my one executable, i can just deploy this in a One-shot deal. It automatically creates the images and dll's it needs and stores them in %temp%. So if i ever have to update it, i'll just have to deploy the exe again.

This means less hassle and minimizes the overhead (and possible mistakes that come with overhead).

If you have any suggestions on documentation or links, please do share them.

Thanks again. Have a great weekend.

Locked