10

In PowerShell you can subscribe to an event by using the add_NameOfEvent({scriptblock})-method of the object. This works well for Form objects like buttons etc. However when I tried it with a System.Timers.Timer it didn't work. Why is that? Sample:

$timer1 = New-Object System.Timers.Timer
$timer1.Interval = 2000
$timer1.add_Elapsed({ Write-Host "Timer1 tick" })

$timer2 = New-Object System.Timers.Timer
$timer2.Interval = 2000
Register-ObjectEvent -InputObject $timer2 -EventName Elapsed -Action { Write-Host "Timer2 tick" }

$timer1.Start()
$timer2.Start()

$timer2 will work fine, but $timer1 will never write to the console. What makes a Timer different from ex. a form-component(where the add_... method works)? Does the Timer run in a seperate thread and because of that, writes to a "hidden" console?

Proof that the method works with form-components for those not familiar with it:

PS > Add-Type -AssemblyName System.Windows.Forms
PS > $b = New-Object System.Windows.Forms.Button
PS > $b.add_click({ Write-Host "button" })
#Get-EventSubscriber won't show this event, but it's added
PS > $b.PerformClick()
button
Frode F.
  • 52,376
  • 9
  • 98
  • 114

2 Answers2

7

if you try this code:

$w = new-object 'System.Windows.Forms.Form'
$timer1 = New-Object System.Timers.Timer
$timer1.Interval = 2000
$timer1.add_Elapsed({ Write-Host "Timer1 tick" })
$timer1.SynchronizingObject = $w
$timer1.Start()
$w.ShowDialog()

you'll see the expected behavior because the System.Timers.Timer can sync with the window form (as in a c# code).

The problem in powershell console is that the timer is created in a different thread and can't sync with the console because it doesn't expose a ISynchronizeInvoke interface representing the object used to marshal the event-handler calls that are issued when an interval has elapsed.

When SynchronizingObject is null, the method that handles the Elapsed event is called on a thread from the system-thread pool.

IMO Behind the scene Register-ObjectEvent create a system-thread pool to accomplish the event-handler calls.

I'm not able to re-create by hands what Register-ObjectEvent do, but I think it possible with some c#/Pinvoke code.

CB.
  • 58,865
  • 9
  • 159
  • 159
  • +1 for good answer. I've read about synchronizingobject before I asked, and I guess the normal add control method you can use to add the control in a form sets the syncronizingobject property also. However, there must be a variable/object I can use to syncronize the timer with the console. I consider this the correct answer, but will wait a couple of days to mark it in case someone can provide a full solution (without creating a dummy-form) :-) – Frode F. Mar 26 '13 at 12:35
  • Graimer I hope @x0n can come to give a better one, he has a deep knowledge on powershell eventing. Anyway I found your question really interesting! – CB. Mar 27 '13 at 09:19
1

This article indicates to me that you must subscribe to events in order for them to be raised in the PowerShell engine. It looks like you have the concept down for $timer2. I've been trying to start $timer1 in my own session and it does not appear to work using the methods exposed, I'm specifically referring to .Start().

Christopher Douglas
  • 1,519
  • 2
  • 9
  • 16
  • I'm aware that you need to subscribe to the event, but that's what the `add_eventname` is a shortcut for. "To have a script block directly handle a .NET event, call that object's Add_Event() method" source: http://www.pavleck.net/powershell-cookbook/ch31.html . As said, this shortcut works with .net form components, so what makes them different from a .net Timer? :S – Frode F. Mar 19 '13 at 17:48
  • This method may also be specific to forms, I'm not sure why its exposed in powershell with the Get-Member -Force cmdlet. However, since add_event() isn't exposed in powershell without using the force param on get-member I have a feeling this might be intentional. I notice also in the source you linked he mentions use of Add_Event(), but never actually uses it. Instead the author registers the event. This documentation indicates that the Timer class does not have an add_event() method. http://msdn.microsoft.com/en-us/library/system.timers.timer_methods.aspx – Christopher Douglas Mar 19 '13 at 17:57
  • `add_eventname` is a "hidden/dynamic" method that PS handles. It isn't visible in any object. If you type `$timer1.add_Elapsed` you will get it's overloads as a proof that it exists. – Frode F. Mar 19 '13 at 18:03
  • I'm seeing the Add_elapsed method overloads, but it takes a .net delegate called ElapsedEventHandler, without the constructor I can't make a new object with it in powershell to pass into add_elapsed method. – Christopher Douglas Mar 19 '13 at 18:26
  • 1
    It takes the value of an eventhandler. I'm not going to write a book here to explain that how it works. The important thing is that it isn't the method's that the problem. It's something else. ex. that the timer runs in a seperate session/thread/whatever so that it isn't connected with my powershell console and because of that can't write to it. I think I need help from superheroes. I need Keith Hill and Shay Levi! :P – Frode F. Mar 19 '13 at 18:45
  • 1
    I have to wonder if that particular method isn't one of those "for internal use only" that mere mortals cannot actually use. I'm not sure why you'd want to anyway. It seems like if you really could get that enabled so that you're generating an event every time the ET changed on that timer, about the only thing you'd accomplish is to eat up a core generating events faster than you could possibly handle them. – mjolinor Mar 22 '13 at 12:39
  • 1
    I don't follow. The action in `add_Elapsed` would only trigger when the timer elapsed(tick), just like when you use `register-objectevent`. However, it's shorter to write (harder to screw up with typos). As for "internal only use", it shouldn't be, as they use it in Technet documentation: http://technet.microsoft.com/en-us/library/ff730941.aspx – Frode F. Mar 26 '13 at 12:38