views:

547

answers:

2

Short version: I think I need help with properly using events in PowerShell that are invoked as a result of a Windows Message to get rid of a balloon tooltip's icon.

Long Version:

I have a long-running PowerShell command (a build) that I would like to be notified when it completes via a balloon tooltip in the system tray/notification area.

I was able to create a Write-BalloonTip script (below) that does roughly what I want. The only problem is that, as sometimes happens with tray icons, the tray icon doesn't disappear until I mouse over it. By re-using the same global variable to represent the NotifyIcon, I'm able to re-use this script and keep it so that only one system tray icon remains (until I mouse over it). This still feels like a hack. I tried to add an event handler so that it'd be notified on the BalloonTipClosed event and then dispose of it there. In the event handler, I tried all three techniques I've seen suggested for getting rid of the lingering icon to no avail.

The annoying part is that a simple .Dispose seems to work on subsequent calls of the script, leading me to think that the event script block isn't being called at all.

I've verified that BalloonTipClosed gets called after the tip fades away in a separate WinForms app.

Am I missing something basic? Any help is much appreciated. Thanks!

Here's the code for "Write-BalloonTip.ps1":

param
(
    $text,
    $title = "",
    $icon = "Info",
    $timeout=15000
)

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | out-null
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | out-null

if ($global:writeBalloonTipIcon)
{
    # This gets rid of the previous one
    $global:writeBalloonTipIcon.Dispose()
}

$global:writeBalloonTipIcon = new-object System.Windows.Forms.NotifyIcon 
$global:writeBalloonTipIcon.Icon = [System.Drawing.SystemIcons]::Information

# FIXME: This *should* cleanup the icon after it's done, but it doesn't seem to work
$global:writeBalloonTipIcon.add_BalloonTipClosed(
  {
    # this *should* work, but it's not. What am I missing?
    $global:writeBalloonTipIcon.Icon = $null; 
    $global:writeBalloonTipIcon.Visible = $false; 
    $global:writeBalloonTipIcon.Dispose();
  });

$global:writeBalloonTipIcon.Visible = $true;
$global:writeBalloonTipIcon.ShowBalloonTip($timeout, $title, $text, $icon);
+2  A: 

I think you need to execute this code in an STA thread. PowerShell (v2 shown here) executes in an MTA thread by default:

PS U:\> [System.Threading.Thread]::CurrentThread


ManagedThreadId    : 5
ExecutionContext   : System.Threading.ExecutionContext
Priority           : Normal
IsAlive            : True
IsThreadPoolThread : False
IsBackground       : False
ThreadState        : Running
ApartmentState     : MTA
CurrentUICulture   : en-US
CurrentCulture     : en-US
Name               : Pipeline Execution Thread
Aaron Lerch
I think this is getting closer to the core of the problem as it seems the notification event is being sent via a Windows message. Any ideas of how to do this the right way? It seems that an Invoke-Apartment should work, but I can't quite get it to work.
Jeff Moser
A: 

I would recommend using the Register-ObjectEvent to subscribe to the BalloonTipClosed event. This came up recently in another SO post. Check it out.

Keith Hill
I think the core of the problem is the STA issue because the event isn't being raised unless it's on a STA thread.
Jeff Moser