5

I'm using the PowerShell ISE (PS version 5.0). If I run this code:

Write-Host "This"

It outputs:

This

If I modify the script like this:

Write-Host "That"

It outputs:

That

Great. As expected. Now, if I have this code:

$Form = New-Object System.Windows.Forms.Form
$Timer = New-Object System.Windows.Forms.Timer

$Timer.Add_Tick(
{
    &{
    Write-Output "Here"
    $Form.Close()} | Write-Host 
})

$Timer.Interval = 3000
$Timer.start()
$result = $Form.ShowDialog()

It outputs:

Here

If I change anything in the script, e.g. "Here" to "There" or $Timer.Interval = 3000 to $Timer.Interval = 4000 and run it, it does two unexpected things: 1.) instead of showing the form for the proper duration of time, it briefly flashes it on the screen, and 2.) it outputs the original Here instead of There. If I close the ISE and re-open it, the script runs as expected.

What is going on?

rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • Well it looks like you're not disposing of the objects... so you're just creating lots of instances of the objects. Starting again from fresh clears out the in-memory objects – gvee Jun 01 '17 at 13:42
  • When you say "run it" - do you run the entire file/code snippet, or just the last part? You can't show a form after having closed it once, and unless you re-instantiate the `$timer`, the original event registration will still exist – Mathias R. Jessen Jun 01 '17 at 13:42
  • @gvee -- so you're saying the ISE is holding onto the objects even after it stops? Coming from .NET and Visual Studio with not much PS experience, I find this strange and unexpected. I'm thinking "objects should automatically be disposed after it stops executing." – rory.ap Jun 01 '17 at 13:43
  • @MathiasR.Jessen ^ – rory.ap Jun 01 '17 at 13:43
  • @rory.ap - in VS it gets disposed of when the debugger is stopped, right? The equivalent in PS ISE is to close the session. – gvee Jun 01 '17 at 13:47
  • @gvee -- Yes that's right. So you can see how I'd assume that when the red "stop" button at the top of the ISE changes to the green "run" button (as it does in VS), I'd assume everything was disposed/reset. – rory.ap Jun 01 '17 at 13:48

2 Answers2

2

tl;dr:

  • The timer instance is created in the session scope,

    • whether or not you run your script in the ISE,
    • and whether or not any variables that reference it are in scope.
  • Always dispose of a timer (or at least disable it) to prevent it from generating more events.

  • Generally - although that is not the cause of the problem at hand - be aware that running a script in the ISE implicitly dot-sources it, so that repeated executions run in the same scope, with variable values from previous ones lingering, which can lead to unexpected behavior.


Your code never disposes of (or disables) the timer, which therefore:

  • stays alive for the entire session, whether or not a variable references it

  • continues to generate events,

  • but they only fire while a form is being displayed.

This explains your symptom: The queued up, original events fire instantly as soon as you display the form again.

The solution is to dispose of the timer once it has done its duty and fired the event (once):

Add-Type -AssemblyName System.Windows.Forms

$Form = New-Object System.Windows.Forms.Form
$Timer = New-Object System.Windows.Forms.Timer

$Timer.Add_Tick({
    & {
      Write-Output "Here"
      $Form.Close()
    } | Write-Host 
})

$Timer.Interval = 3000
$Timer.Start()
$result = $Form.ShowDialog()
$Timer.Dispose() # IMPORTANT: Dispose of the timer so it won't generate more events.

Even with the implicit sourcing behavior of the ISE described above, repeated invocations of this code work as expected.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

I think it has to do with how variables in the ISE are still in memory even after the script ends. If you add

$Timer.Stop() 

to the last line of the script then close and reopen the ISE it will work.

Zach Alexander
  • 403
  • 4
  • 9