0

TLDR: How can I make a generated variable, and then call that variable later within a Add_click. I am sure some kind of serialization of each Object/button I make is what is needed.

I am building a small tool that reads from a csv to create a button, and function.

the csv looks something like

Name  Type  Link  Script
Powershell App C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe Empty
FixXYZ Fix Empty -ScriptStuffHere- 

The tool will then make a button with the Name, (work in progress to filter apps and fixes), and when you click the button, if its an app will do start ($link) and if its a fix it will run that script.

My issue is I have it making the button and giving them names, and the name of the button stays, but the function does not.

full code:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()

#=======================================================

$Form                            = New-Object system.Windows.Forms.Form
$Form.text                       = "Form"
$Form.TopMost                    = $false
$Form.ClientSize                 = New-Object System.Drawing.Point(760,400)
$Form.minimumSize                = New-Object System.Drawing.Size(760,400) 
$Form.maximumSize                = New-Object System.Drawing.Size(760,400) 

$GetCSV = import-csv "C:\File.csv"
$Details = $GetCSV.Name

$DeviceList = $GetCSV

$Count = $DeviceList.Lines.Count
$ObjectNumber = -1
Write-Host "Total Entries:" $Count


$x = 0 #up down
$z = 0 #left right


$Names = @($DeviceList.Lines)
$Names | ForEach-Object{
$ObjectNumber += 1
Write-Host "Object:" $ObjectNumber 

$x += 0
$z += 120

if($z -eq 720){
$x += 120
$z = 0
Write-Host "New Row"}



Write-Host "x" $x
Write-Host "z" $z
$ButtonLabel = ($GetCSV[$ObjectNumber]).Name

set-Variable -Name "var$ObjectNumber" -Value ($GetCSV[$ObjectNumber] | Select Name, Type, Link, Script, File, FileSource)

Write-Host "Name: " (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Name
Write-Host "Type: " (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Type
Write-Host "Link: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link
Write-Host "Script: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).Script
Write-Host "File: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).File
Write-Host =========================

         
$_                         = New-Object system.Windows.Forms.Button
$_.text                    = $ButtonLabel
$_.width                   = 100
$_.height                  = 100
$_.location                = New-Object System.Drawing.Point($z,$x)
$_.Font                    = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$_.Add_Click({              Start (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link})

$Form.Controls.Add($_)

}

[void]$Form.ShowDialog()

I am very certain my issue is coming from $_.Add_Click({Start (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link})

I know the issue is with $ObjectNumber because that number is getting +1 each time the ForEach is gone through, so when I click a button, its taking "var$OjbectNumber" as its Last number. Clicking the button works, but all buttons open the last entries link.

1
  • Does that file really look like that??? If so, that is not a properly formatted Csv, nor are you specifying a delimiter on the Import-Csv effort. You are not handling multiple spaces, etc. This $DeviceList.Lines.Count, is not valid, The property 'Lines' cannot be found on this object. Verify that the property exists. This $x += 0, also never gets set. Just curious. However, why the CSV thing at all, when you can WPF/XAML and a dot sources code file to call from by function name? You have other errors as well. That addClick is also wrong because of the above and logic in use. Commented Jan 21, 2021 at 4:18

3 Answers 3

1

The answer was using a unused property to throw my desired call back variable in. So in this case, i have a folder with with programs, the button will be made, and set the $Button.Text (its name) as the name of the .exe, and then it sets the $Button.Tag as the file path, so when I go do the button.Add_Click , I just call the Button.Tag as it will have the path of my Exe.

    Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form                            = New-Object system.Windows.Forms.Form
$Form.ClientSize                 = '580,400'
$Form.Text                       = "Test"
$Form.TopMost                    = $false
$Form.FormBorderStyle            = 'Fixed3D'
$Form.MaximizeBox                = $false
$Form.minimumSize                = New-Object System.Drawing.Size(580,400) 
$Form.maximumSize                = New-Object System.Drawing.Size(580,400) 

#Place Holder Form Junk Above




#Reset these on Run
$Global:x             = 10 #Reset up down
$Global:z             = 10 #Reset left right
$Global:ObjectNumber = -1 #Reset Object Count






Function Make-Button([string] $ToolName, [string] $ToolPath, [string] $SetZ, [string] $SetX){
$Button                         = New-Object system.Windows.Forms.Button
$Button.text                    = $ToolName
$Button.width                   = 120
$Button.height                  = 120
$Button.location                = New-Object System.Drawing.Point($SetZ,$SetX)
$Button.Font                    = New-Object System.Drawing.Font('Franklin Gothic',10)
$Button.FlatStyle           = [System.Windows.Forms.FlatStyle]::Flat
$Button.FlatAppearance.BorderSize  = 0
$Button.ForeColor           = [System.Drawing.ColorTranslator]::FromHtml("#ffffff")
$Button.BackColor           = [System.Drawing.ColorTranslator]::FromHtml("#515582")

$Button.tag = $ToolPath #<- this is where the answer was. Throwing my desired callback into an unused property of the the Button. in this case, i used _.Tag
$Button.Add_Click{start $this.tag}

$Form.Controls.AddRange(@($Button))

Write-Host "$ToolName"
Write-Host "$ToolPath"
Write-Host "$SetZ"
Write-Host "$SetX"

}

function Get-Position{
switch ($Global:ObjectNumber) {
-1{$Global:ObjectNumber += 1
Write-Host "Object:" $Global:ObjectNumber 

$Global:x = 0
$Global:z += 0}


Default{$Global:ObjectNumber += 1
Write-Host "Object:" $Global:ObjectNumber 

$Global:x += 0
$Global:z += 140}
}#end switch

if($Global:z -eq 570){ #Make New Row
$Global:x += 140
$Global:z = 10
Write-Host "New Row"
}
}


$Tools = Get-ChildItem "C:\WINDOWS\system32" -Filter *.exe
$Count = ( $Tools | Measure-Object ).Count;
Write-Host "Entries:" $Count





$Names = @($Tools) #Put Tools in Array
$Names | ForEach-Object{

                        Get-Position
                        Make-Button ($_.Name).replace(".exe","") ($_.FullName) ($z) ($x)

}











#End Form
$Test.Add_Shown(            {$Test.Activate()})
$Test.ShowDialog() 
[void]$Form.ShowDialog()
Sign up to request clarification or add additional context in comments.

2 Comments

@postanote I found a way to do this. Code posted above if you wanna give it a look
Probably the best button generating PowerShell scripts with function I've seen. The form would be really good for a script that could remotely connect to a network computer and generate a form with buttons for each installed app and the button click could uninstall the app.
0

Continuing from my comment...

A small refactor to get this to show where things are

Clear-Host 

Add-Type -AssemblyName System.Windows.Forms,
                       PresentationFramework

[System.Windows.Forms.Application]::EnableVisualStyles()

$Form             = New-Object system.Windows.Forms.Form
$Form.text        = 'Form'
$Form.TopMost     = $false
$Form.ClientSize  = New-Object System.Drawing.Point(760,400)
$Form.minimumSize = New-Object System.Drawing.Size(760,400) 
$Form.maximumSize = New-Object System.Drawing.Size(760,400) 

$GetCSV       = Import-Csv -LiteralPath 'D:\Scripts\File.csv'
$Details      = $GetCSV.Name
$DeviceList   = $GetCSV
$Count        = $DeviceList.Count
$ObjectNumber = -1
 "Total Entries: $Count`n`n"

$ObjDown  = 0
$ObjRight = 0

$DeviceList.Name | 
ForEach-Object{
    $ObjectNumber += 1
    "`nObject: $ObjectNumber"

    $x        = 0
    $ObjRight = 120

    if($ObjRight -eq 720)
    {
        $x        = 120
        $ObjRight = 0
        'New Row'
    }


    "x $x"
    "z $ObjRight"

    $ButtonLabel = ($GetCSV[$ObjectNumber]).Name

    set-Variable -Name $("var$ObjectNumber") -Value ($GetCSV[$ObjectNumber] | 
    Select Name, Type, Link, Script, File, FileSource)

    ("Name:   $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Name)")
    ("Type:   $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Type)")
    ("Link:   $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Link)")
    ("Script: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Script)")
    ("File:   $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).File)")
         
    $PSitem          = New-Object system.Windows.Forms.Button
    $PSitem.text     = $ButtonLabel
    $PSitem.width    = 100
    $PSitem.height   = 100
    $PSitem.location = New-Object System.Drawing.Point($ObjRight,$x)
    $PSitem.Font     = New-Object System.Drawing.Font('Microsoft Sans Serif',10)

    $PSitem.Add_Click({
        $(Get-Variable -Name $("var$ObjectNumber") -ValueOnly)
    })
    $Form.Controls.Add($PSitem)
}

#[void]$Form.ShowDialog()

Here is an example I gave as an answer to another post to dynamically create UX/UI elements and assign a form event, though not using an external file, it's the same concept.

How to create multiple button with PowerShell?

Add tooltip and form event, like so...

$Form                            = New-Object system.Windows.Forms.Form
$Form.ClientSize                 = New-Object System.Drawing.Point(381,316)
$Form.text                       = "Auto Button UI"
$Form.TopMost                    = $false
$Form.BackColor                  = [System.Drawing.ColorTranslator]::FromHtml("#c9f6fe")

$i = 0
Get-Variable -Name 'Button*' | 
Remove-Variable

$objTooltip = New-Object System.Windows.Forms.ToolTip 
$objTooltip.InitialDelay = 100 

1..3 | 
foreach{
    $CurrentButton          = $null
    $CurrentButton          = New-Object System.Windows.Forms.Button
    $CurrentButton.Location = "$(50+100*$i), 275"
    $CurrentButton.Text     = $PSItem
    $CurrentButton.Font     = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
    
    New-Variable "Button$PSitem" $CurrentButton

    $objTooltip.SetToolTip(
        $CurrentButton, 
        "Execute action assigned to $($CurrentButton.Text)"
    )

    $CurrentButton.add_click(
    {
        [System.Windows.Forms.MessageBox]::
        Show(
            "$($CurrentButton.Text)", $($CurrentButton.Text), [System.Windows.Forms.MessageBoxButtons]::
            OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
        )
    })

    $i++
    $form.Controls.Add($CurrentButton)
}

[void]$Form.ShowDialog()

Yet, though it adds the event to each button element, the message text is the last one passed. Unless explicitly called as in the example from the link.

4 Comments

So this is why I am confused, in that example you gave, the tooltip is relavant to the buttons number/name, but when you click on it, it shows a pop up with the last buttons name no matter what button I click. But both the tooltip and the button info is being called the same with with $($CurrentButton.Text) .. Why wont the stuff in the message box show up correctly while the tooltip is?
My samples point to the restriction. Text-based stuff is easily dealt with/manipulated, events are a 1:1 thing/static assignment. The event variable name used can only be one specific object name. So, what you are after on the events, is not possible that I am aware of (and I did try a few things) or ever seen anyone else try; nor does any UX/UI developer reference, MSDocs or 3rdP docs address such a use case, that I could find.
That makes sense. I will try and find a different way at going about auto populating my buttons.
Remember, the deal here is that it is this line $CurrentButton = New-Object System.Windows.Forms.Button, that is the catch22. It must be unique. Meaning one has to exits for every element you create, thus the reason you are only seeing the last in, because each loop, it gets re-created with the next item. One object per name. Lastly, this $form.Controls.Add($CurrentButton) needs to add each, via .AddRange or .Add. Again, that name is static, and of course, pinning that to your needed event for that object/element.
0

To adapt the second example in the answer already provided here so that the message text is not just the last one passed, you can change the reference within the event to the instance this.text rather than the iteratively updated $CurrentButton.text

    $CurrentButton.add_click(
    {
        [System.Windows.Forms.MessageBox]::
        Show(
            "$($this.Text)", $($this.Text), [System.Windows.Forms.MessageBoxButtons]::
            OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
        )
    })

Credit to jrv https://social.technet.microsoft.com/Forums/ie/en-US/09ff4141-6222-4bff-b8a9-a1253e0d378a/powershell-form-procedurally-creating-buttons?forum=ITCG

Full code with serialization of button object and event:

Clear-Host 

Add-Type -AssemblyName System.Windows.Forms,
                       PresentationFramework

[System.Windows.Forms.Application]::EnableVisualStyles()

$Form                            = New-Object system.Windows.Forms.Form
$Form.ClientSize                 = New-Object System.Drawing.Point(381,316)
$Form.text                       = "Auto Button UI"
$Form.TopMost                    = $false
$Form.BackColor                  = [System.Drawing.ColorTranslator]::FromHtml("#c9f6fe")

$i = 0
Get-Variable -Name 'Button*' | 
Remove-Variable

$objTooltip = New-Object System.Windows.Forms.ToolTip 
$objTooltip.InitialDelay = 100 

1..3 | 
foreach{
    $CurrentButton          = $null
    $CurrentButton          = New-Object System.Windows.Forms.Button
    $CurrentButton.Location = "$(50+100*$i), 275"
    $CurrentButton.Text     = $PSitem
    $CurrentButton.Font     = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
    
    New-Variable "Button$PSitem" $CurrentButton

    $objTooltip.SetToolTip(
        $CurrentButton, 
        "Execute action assigned to $($CurrentButton.Text)"
    )

    $CurrentButton.add_click(
    {
        [System.Windows.Forms.MessageBox]::
        Show(
            "$($this.Text)", $($this.Text), [System.Windows.Forms.MessageBoxButtons]::
            OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
        )
    })

    $i++
    $form.Controls.Add($CurrentButton)
}

[void]$Form.ShowDialog()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.