1

The code below will correctly display a message box when the notification icon is clicked.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$NotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$NotifyIcon.Icon = New-Object System.Drawing.Icon(Join-Path $PSScriptRoot "icon.ico")
$NotifyIcon.Visible = $True

Register-ObjectEvent -InputObject $NotifyIcon -EventName Click -Action {
    Write-Host "Callback called."
    [System.Windows.Forms.MessageBox]::Show("Test") 
}

(The program won't work without a correctly specified icon file. You can download one icon here, for example.)

If I move the line displaying the message box into a function, the code fails to display that message box:

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

function Do-Something {
    Write-Host "Do-Something called."
    [System.Windows.Forms.MessageBox]::Show("Test") 
}

$NotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$NotifyIcon.Icon = New-Object System.Drawing.Icon(Join-Path $PSScriptRoot "icon.ico")
$NotifyIcon.Visible = $True

Register-ObjectEvent -InputObject $NotifyIcon -EventName Click -Action {
    Write-Host "Callback called."
    Do-Something
}

The first strange aspect is that the second code writes Callback called to the console - but only once. After one click, it will no longer print any debug messages.

I ran both scripts using powershell.exe and typing ./script[1|2].ps1. (I don't recommend running both scripts simultaneously since they use the same icon. Closing the PowerShell window terminates the script and deletes the icon resource. The icon stays present until you move your mouse over it, however.)

The second strange fact appears when running the scripts via the Windows PowerShell ISE: They both work like a charm.

Trying to set breakpoints in the two lines of the callback handler in the second script didn't work for me. PowerShell ISE always gives a warning when exactly those lines are executed:

PS P:\...> WARNUNG: Haltepunkt Zeilenhaltepunkt bei "P:\...\script.ps1:13" wird nicht erreicht.
Callback called.
WARNUNG: Haltepunkt Zeilenhaltepunkt bei "P:\...\script.ps1:14" wird nicht erreicht.
Do-Something called.

Translation from German into English:

PS P:\...> WARNING: Breakpoint line breakpoint at "P:\...\script.ps1:13" doesn't get reached.
Callback called.
WARNING: Breakpoint line breakpoint at "P:\...\script.ps1:14" doesn't get reached.
Do-Something called.

The same problem occurs when trying to debug the first script.

Searching for the issue in the Internet turned out to be rather hard. I tried powershell net callback called once (among others), but I couldn't find anything.

ComFreek
  • 29,044
  • 18
  • 104
  • 156
  • For what it's worth i always have testing issues in ISE since scripts and code that are run variables are maintained since the session does not terminate. Also, maybe you are having a scope issue? – Matt Aug 17 '14 at 17:49
  • @Matt Indeed, I've also observed that behavior. I therefore restarted the ISE every time I wanted to test the script again. Your mention of scope issues has let me reinvestigate into that topic and led to a solution (which I'll post). Thanks! – ComFreek Aug 17 '14 at 18:33

1 Answers1

1

The Problem

The problem that Do-Something could not be called is due to scope issues, I suppose. Since Register-ObjectEvent returns an event job if an action is provided, I think the execution of the action block acts like the one of jobs, which run in a different PowerShell process.

Another supportive argument would be that calling Do-SomethingXYZ (which obvisouly doesn't exist) doesn't raise any (visible?) errors.

My Solution

Suggestions for more idiomatic solutions are welcome!

  1. Use the MessageData parameter of Register-ObjectEvent to overcome the scope problem.

  2. Use ${function:xyz} to pass a function as a parameter.

  3. Invoke the function using $Event.MessageData.Invoke().

The code:

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

function Do-Something {
    Write-Host "Do-Something called."
    [System.Windows.Forms.MessageBox]::Show("Test") 
}

$NotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$NotifyIcon.Icon = New-Object System.Drawing.Icon(Join-Path $PSScriptRoot "icon.ico")
$NotifyIcon.Visible = $True

Register-ObjectEvent -MessageData ${function:Do-Something} -InputObject $NotifyIcon -EventName Click -Action {
    Write-Host "Callback called."
    $Event.MessageData.Invoke()
}

Further comments

The first strange aspect: Callback called only once?

The first strange aspect is that the second code writes Callback called to the console - but only once. After one click, it will no longer print any debug messages.

— ComFreek (in the question)

The code is as follows:

Write-Host "Callback called."
Do-Something

I admit, the debug message is a bit misleading. At the time of writing the message via Write-Host, Do-Something hasn't been called yet. Since PowerShell is not able to resolve Do-Something, it silently fails. That is probably fatal enough to stop calling the action handler, registered by Register-ObjectEvent, a second time. Therefore, the debug message only appears once.

The second strange aspect: Why do both scripts work with PowerShell ISE?

This question is not fully resolved, but I might just add the information I have observed so far.

The program acts like a REPL (Read-eval-print loop) equipped with many useful features. The point is that it only uses one PowerShell session. You can observe that behavior by running a script containing $a = "Hello", clearing the file and then outputting the value by running $a. All objects (variables, functions, etc.) remain alive and share the same scope with other script executions.

Open questions / questions you might consider

  1. Why does PowerShell not throw an error when an unknown function in an action handler is called?
    Is there something like an error level which restricts certain kinds of errors from being shown?

    Passing -ErrorAction stop to Register-ObjectEvent does not help.

  2. Why do the breakpoints apparently not get reachted, but their associated line executed?

Answers or comments to these questions are more than welcome :)

Community
  • 1
  • 1
ComFreek
  • 29,044
  • 18
  • 104
  • 156