megahendrik
megahendrik

Reputation: 27

PowerShell Add_Click in foreach loop

What I am trying to accomplish is to create buttons that launch exe files in a certain directory when clicked, but when I try using a foreach loop to create a few buttons, all of the buttons just launch the file the last button is supposed to launch.

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form = New-Object System.Windows.Forms.Form
$form.Text = 'Main Window'
$form.Size = New-Object System.Drawing.Size(600,400)

$flp = New-Object System.Windows.Forms.FlowLayoutPanel
$flp.Location = New-Object System.Drawing.Point(0,0)
$flp.Height = $form.Height
$flp.Width = $form.Width
$form.Controls.Add($flp)

$files = Get-ChildItem "$home\Downloads" -Include *.exe -Name

foreach ($file in $files){
    $button = New-Object System.Windows.Forms.Button
    $flp.Controls.Add($button)
    $button.Width = 100
    $button.Height = 50
    $button.Text = $file
    $button.Add_Click{
        Start-Process -FilePath "$home\Downloads\$file"
    }
}

$form.Topmost = $true
$form.ShowDialog()

Whatever I'm doing is probably pretty stupid, so I was just looking for any alternatives or solutions to this other than to just hard code everything.

Upvotes: 2

Views: 1293

Answers (1)

Santiago Squarzon
Santiago Squarzon

Reputation: 59822

It is likely that you need to use .GetNewClosure() ScriptBlock method so that each script block (button click event) holds the current value of the $file variable at the moment of enumeration.

Example of what this means:

$blocks = foreach($i in 0..5) {
    { "hello $i" }
}
& $blocks[0] # => hello 5
& $blocks[1] # => hello 5

$blocks = foreach($i in 0..5) {
    { "hello $i" }.GetNewClosure()
}
& $blocks[0] # => hello 0
& $blocks[1] # => hello 1

In that sense, and assuming this is the issue, the following should work:

foreach ($file in $files) {
    $button = [System.Windows.Forms.Button]@{
        Width  = 100
        Height = 50
        Text   = $file
    }
    $button.Add_Click(
        { Start-Process -FilePath "$home\Downloads\$file" }.GetNewClosure())
    $flp.Controls.Add($button)
}

A nice alternative to having a need to use .GetNewClosure() can be seen on this answer. The .Tag property of the Button can be used to store the information of the file's path which then can be used on the button's .Click event:

foreach ($file in $files) {
    $button = [System.Windows.Forms.Button]@{
        Width  = 100
        Height = 50
        Text   = $file
        Tag    = "$home\Downloads\$file"
    }
    $button.Add_Click({ Start-Process -FilePath $this.Tag })
    $flp.Controls.Add($button)
}

Upvotes: 2

Related Questions