Second question today but this time I believe there's something really strange going on. The following code is not my original, I tried to reproduce the behavior with as few lines as possible. In my original code I'm importing the application paths and names with a config file and call the CreateButton function in a foreach loop.
I'm trying to create multiple buttons with a function wich should open different applications. When I hardcode the Path everything is working. when I use variables in a foreach loop every button uses the LAST path submitted. Here is code that works:
# Define Form
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object Windows.Forms.Form
$form.Size = New-Object Drawing.Size @(260,250)
$Form.Text = "ToolBox"
$form.StartPosition = "CenterScreen"
# Create Button Function
function CreateButton ($ButtonCommand, $ButtonName, $ButtonLocX, $ButtonLocY, $ButtonSizeX, $ButtonSizeY)
{
$btn = New-Object System.Windows.Forms.Button
$btn.add_click($ButtonCommand)
$btn.Text = $ButtonName
$btn.Location = New-Object System.Drawing.Size($ButtonLocX,$ButtonLocY)
$btn.Size = New-Object System.Drawing.Size($ButtonSizeX,$ButtonSizeY)
$form.Controls.Add($btn)
}
$Command={Start-Process -FilePath "C:\Windows\system32\mmc.exe"}
$Text='MMC'
$LocationX=20
$LocationY=10
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
$Command={Start-Process -FilePath "C:\Windows\regedit.exe"}
$Text='Regedit'
$LocationX=20
$LocationY=70
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
$Command={Start-Process -FilePath "C:\Windows\System32\cmd.exe"}
$Text='CMD'
$LocationX=20
$LocationY=100
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
#Show Form
$drc = $form.ShowDialog()
Here is Code that does not work:
# Define Form
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object Windows.Forms.Form
$form.Size = New-Object Drawing.Size @(260,250)
$Form.Text = "ToolBox"
$form.StartPosition = "CenterScreen"
# Create Button Function
function CreateButton ($ButtonCommand, $ButtonName, $ButtonLocX, $ButtonLocY, $ButtonSizeX, $ButtonSizeY)
{
$btn = New-Object System.Windows.Forms.Button
$btn.add_click($ButtonCommand)
$btn.Text = $ButtonName
$btn.Location = New-Object System.Drawing.Size($ButtonLocX,$ButtonLocY)
$btn.Size = New-Object System.Drawing.Size($ButtonSizeX,$ButtonSizeY)
$form.Controls.Add($btn)
}
$Global:Test="C:\Windows\system32\mmc.exe"
$Command={Start-Process -FilePath $Global:Test}
$Text='MMC'
$LocationX=20
$LocationY=10
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
$Global:Test="C:\Windows\regedit.exe"
$Command={Start-Process -FilePath $Global:Test}
$Text='Regedit'
$LocationX=20
$LocationY=70
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
$Global:Test="C:\Windows\System32\cmd.exe"
$Command={Start-Process -FilePath $Global:Test}
$Text='CMD'
$LocationX=20
$LocationY=100
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
#Show Form
$drc = $form.ShowDialog()
The first example creates a form with 3 buttons. When I click each button different applications get started. The second example creates a form with 3 buttons. When I click each button "CMD.EXE" gets opened. When I change the CMD.EXE with the MMC.EXE block in the code each button will open the MMC. Can anyone explain whats going on?
Essentially you want, that different instance of same script block produce a different results. For that to happen you have to attach different context to each script block instance. One way to do this is to bind script block to module. For that you can use GetNewClosure()
script block instance method:
$ScriptBlockWithContext=$ScriptBlock.GetNewClosure()
This method will create new module and capture all current scope variables into it. If you want to have more control about what to be captured, than you can create module by yourself:
$Module=New-Module {$Context=$Global:Context}
$ScriptBlockWithContext=$Module.NewBoundScriptBlock($ScriptBlock)
If you want to refer local variable, then you can pass them as additional parameters to New-Module cmdlet:
$Module=New-Module {param($Context)} $Context
$ScriptBlockWithContext=$Module.NewBoundScriptBlock($ScriptBlock)
Alternatively, you can invoke operator &
or .
to invoke arbitrary code in module scope after you create it. In particular, you can use New-Variable
or Set-Variable
cmdlet to set variables inside module scope:
$Module=New-Module {}
& $Module New-Variable Context $Context
$ScriptBlockWithContext=$Module.NewBoundScriptBlock($ScriptBlock)
Here simple modification to your code to make it work:
# Define Form
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object Windows.Forms.Form
$form.Size = New-Object Drawing.Size @(260,250)
$Form.Text = "ToolBox"
$form.StartPosition = "CenterScreen"
# Create Button Function
function CreateButton ($ButtonCommand, $ButtonName, $ButtonLocX, $ButtonLocY, $ButtonSizeX, $ButtonSizeY)
{
$btn = New-Object System.Windows.Forms.Button
$btn.add_click($ButtonCommand)
$btn.Text = $ButtonName
$btn.Location = New-Object System.Drawing.Size($ButtonLocX,$ButtonLocY)
$btn.Size = New-Object System.Drawing.Size($ButtonSizeX,$ButtonSizeY)
$form.Controls.Add($btn)
}
$CaptureCommand={param($Test)}
$CommonCommand={Start-Process -FilePath $Test}
$Module=New-Module $CaptureCommand "C:\Windows\system32\mmc.exe"
$Command=$Module.NewBoundScriptBlock($CommonCommand)
$Text='MMC'
$LocationX=20
$LocationY=10
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
$Module=New-Module $CaptureCommand "C:\Windows\regedit.exe"
$Command=$Module.NewBoundScriptBlock($CommonCommand)
$Text='Regedit'
$LocationX=20
$LocationY=70
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
$Module=New-Module $CaptureCommand "C:\Windows\System32\cmd.exe"
$Command=$Module.NewBoundScriptBlock($CommonCommand)
$Text='CMD'
$LocationX=20
$LocationY=100
$SizeX=200
$SizeY=30
CreateButton $Command $Text $LocationX $LocationY $SizeX $SizeY
#Show Form
$drc = $form.ShowDialog()