Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The PowerShell ISE sometimes behaves unpredictably after code changes are made

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?

like image 934
rory.ap Avatar asked Oct 17 '22 11:10

rory.ap


1 Answers

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.

like image 100
mklement0 Avatar answered Nov 04 '22 19:11

mklement0