Powershell GUI auto generate buttons with functions - winforms

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.

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()

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.

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()

Related

Struggling with an Until Loop combined with Get-Random and a Mouse Click in a PowerShell Form

I am struggling with an until loop in combination with a random picker and a mouse click in a PowerShell Form.
I am able to run a random picker without the form , where I have weekdays, picked by random and one by one day, is being removed, until the arraylist is empty. Works not bad.
$Weekdays = 'Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'
[System.Collections.ArrayList]$arraylist = $Weekdays
Write-Host $arraylist -ForegroundColor Green
pause
do {
$removetask = Get-Random $arraylist.ToArray()
$arraylist.Remove($removetask)
Write-Host $removetask
Write-Host $arraylist -ForegroundColor Red
pause
} until ($arraylist.Count -eq 0)
In another approach, I tried to do the same, but this time, I want to control the looping itself, that as soon as the first key from the arraylist is taken and shown in a label, I have to click the mouse button, so it continues to take the next random.
Without the do {} until () I got so far:
$TestForm = New-Object System.Windows.Forms.Form
$TestForm.Size = New-Object System.Drawing.Size (1200,800)
$TestForm.Text ='Random Test'
$TestForm.StartPosition = "CenterScreen"
$TestForm.AutoSize = $true
$TestForm.BringToFront()
$TestForm.BackgroundImageLayout = "Stretch"
[System.Collections.ArrayList]$Weekdays = 'Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'
$TestLabel = New-Object System.Windows.Forms.label
$TestLabel.Location = New-Object System.Drawing.Size '500,200'
$TestLabel.Size = New-Object System.Drawing.Size '600,60'
$TestLabel.Font = New-Object System.Drawing.Font ('Times New Roman','20',[System.Drawing.FontStyle]::Bold)
$TestLabel.BackColor = 'Transparent'
$TestLabel.ForeColor = "Blue"
$removetask = Get-Random $Weekdays.ToArray()
$TestLabel.Text = $removetask
$TestForm.Controls.Add($TestLabel)
$TestButton = New-Object System.Windows.Forms.Button
$TestButton.Location = New-Object System.Drawing.Size '500,600'
$TestButton.Size = New-Object System.Drawing.Size '200,75'
$TestButton.Font = New-Object System.Drawing.Font ('Arial','10',[System.Drawing.FontStyle]::Bold)
$TestButton.Text = 'Next Random'
$TestForm.Controls.Add($TestButton)
$TestButton.Add_Click()
$TestForm.ShowDialog()
$TestForm.Dispose()
Now I have a few lines of code left, I am not able to include on such way, so it works on the following way.
The testform opens, and in a label I see chosen by random one of the weekdays. Clicking next will remove the chosen weekday from the arraylist and show the next weekday by random and will continue until the arraylist is empty.
The missing pieces to the puzzle are:
### The loop itself
do {} until ()
### code to find a Random value from $weekdays and write it into $removetask
$removetask = Get-Random $Weekdays.ToArray()
### code to remove the randomly chosen day and remove it from the arraylist
$Weekdays.Remove($removetask)
#### check if array is empty
($weekdays.Count -eq 0)
I was playing around with the codes and tried with Button.Add_Click() and also with ButtonClickEvent {} but either, the loop is not running, the counter is not working correctly or I somehow messed up the code on such way, that it is stuck somewhere, that not even the form is being shown.
The following enhanced adjustment of your script implements some kind of a loop in the form.
Note that no loop keywords (like do, while, until) and even no if keyword are used:
### Load Assemblies for creating form & controls ###
if ( -not ("System.Windows.Forms.Form" -as [type]) ) {
Add-Type -AssemblyName System.Windows.Forms
}
if ( -not ("System.Drawing.Font" -as [type]) ) {
Add-Type -AssemblyName System.Drawing
}
$TestForm = New-Object System.Windows.Forms.Form
$TestForm.Size = New-Object System.Drawing.Size (1200,800)
$TestForm.Text ='Random Test'
$TestForm.StartPosition = "CenterScreen"
$TestForm.AutoSize = $true
$TestForm.BringToFront()
$TestForm.BackgroundImageLayout = "Stretch"
$TestLabel = New-Object System.Windows.Forms.label
$TestLabel.Location = New-Object System.Drawing.Size '500,200'
$TestLabel.Size = New-Object System.Drawing.Size '600,60'
$TestLabel.Font = New-Object System.Drawing.Font ('Times New Roman','20',[System.Drawing.FontStyle]::Bold)
$TestLabel.BackColor = 'Transparent'
$TestLabel.ForeColor = "Blue"
$TestForm.Controls.Add($TestLabel)
$TestLabe2 = New-Object System.Windows.Forms.Label
$TestLabe2.Location = New-Object System.Drawing.Size '200,300'
$TestLabe2.Size = New-Object System.Drawing.Size '900,200'
$TestLabe2.Font = New-Object System.Drawing.Font ([System.Windows.Forms.Label]::DefaultFont.Name,'16',[System.Drawing.FontStyle]::Italic)
$TestLabe2.BackColor = 'Transparent'
$TestLabe2.ForeColor = [System.Drawing.Color]::MidnightBlue
$TestForm.Controls.Add($TestLabe2)
$TestButton = New-Object System.Windows.Forms.Button
$TestButton.Location = New-Object System.Drawing.Size '500,600'
$TestButton.Size = New-Object System.Drawing.Size '200,75'
$TestButton.Font = New-Object System.Drawing.Font ('Arial','10',[System.Drawing.FontStyle]::Bold)
$TestButton.Text = 'Next Random'
$TestForm.Controls.Add($TestButton)
$TestButtoX = New-Object System.Windows.Forms.Button
$TestButtoX.Location = New-Object System.Drawing.Size '200,600'
$TestButtoX.Size = New-Object System.Drawing.Size '200,75'
$TestButtoX.Font = New-Object System.Drawing.Font ('Arial','10',[System.Drawing.FontStyle]::Bold)
$TestButtoX.Text = 'Next Round'
$TestButtoX.Enabled = $false
$TestForm.Controls.Add($TestButtoX)
Function Swap-Buttons {
$TestButton.Enabled = [bool]$script:Weekdays.Count
$TestButtoX.Enabled = -not [bool]$script:Weekdays.Count
}
Function RemoveWeekday {
$script:removetask = Get-Random $script:Weekdays.ToArray()
$script:Weekdays.Remove($script:removetask)
$TestLabe2.Text = ('(remain {0})' -f $script:Weekdays.Count), ($script:Weekdays -join ', ') -join ': '
$TestLabel.Text = $script:removetask
Swap-Buttons
}
Function DefineWeek {
$script:Weekdays = [System.Collections.ArrayList]([System.Enum]::GetNames([System.DayOfWeek]))
<#
# debugging: try another array list (a larger one)
$script:Weekdays = [System.Collections.ArrayList]([System.Drawing.Color] |
Get-Member -MemberType Properties -Static -Force |
Where-Object Name -match ".+blue" |
Select-Object -ExpandProperty Name
)
<##>
}
$TestButton.Add_Click({
RemoveWeekday
})
$TestButtoX.Add_Click({
DefineWeek
$TestButtoX.Enabled = $false
$TestButton.Enabled = $true
RemoveWeekday
})
$script:removetask = ''
DefineWeek
RemoveWeekday
$TestForm.ShowDialog()
$TestForm.Dispose()

How to close form GUI after checking existing file in PowerShell?

I want to check an existing file, if the process still waiting for the file, it will display a GUI window. After the file is exist, the window will close automatically.
I tried this code, the window can not close, even the file already exist.
Checking the file:
$SN = "708TSTA"
$MAC = "2E5961370"
function Find {
$n = 0
while (-not (Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"})) {
Start-Sleep -s 1
D:\Auto\GUI.ps1
$n++
(Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"})
Write-Host "Attempt no $n"
}
Write-Host ">>Flag found after $n attempts"
return $true
}
if (Find) {
Write-Host "Found"
}
GUI.ps1:
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = '578,400'
$Form.Text = "Form"
$Form.BackColor = "#c1daf7"
$Form.WindowState = 'Maximized'
$Form.FormBorderStyle = "FixedDialog"
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$Check = Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"}
if($Check) {
Write-Host "File Exist"
$Form.Close()
}
$Form.Controls.AddRange(#($Label1,$Label2))
[void]$Form.ShowDialog()
Instead of doing Start-Sleep inside the GUI, it is better to use a timer so the form stays responsive.
I changed the code of the GUI.ps1 (not the way it looks) like this:
Param (
[string]$Path = '*.*',
[string]$MaxAttempts = 5
)
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# set things up for the timer
$script:nAttempts = 0
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000 # 1 second
$timer.Add_Tick({
$global:Result = $null
$script:nAttempts++
$file = Get-Item -Path $Path
if ($file) {
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
elseif ($script:nAttempts -ge $MaxAttempts) {
$global:Result = [PSCustomObject]#{
Exists = $false
FileName = ''
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
})
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = '578,400'
$Form.Text = "Form"
$Form.BackColor = "#c1daf7"
$Form.WindowState = 'Maximized'
$Form.FormBorderStyle = "FixedDialog"
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$Form.Controls.AddRange(#($Label1,$Label2))
# start the timer as soon as the dialog is visible
$Form.Add_Shown({ $timer.Start() })
[void]$Form.ShowDialog()
# clean up when done
$Form.Dispose()
And to call it from your other script, use:
$SN = "708TSTA"
$MAC = "2E5961370"
function Test-FileExists {
$file = Get-Item -Path "D:\*$SN-$MAC*"
if ($file) {
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Attempts = 1
}
}
else {
& "D:\GUI.ps1" -Path "D:\*$SN-$MAC*" -MaxAttempts 3
}
}
# call the function that can call the GUI.ps1 script
Test-FileExists
# check the Global result object
if ($global:Result.Exists) {
Write-Host "File '$($global:Result.FileName)' Exists. Found after $($global:Result.Attempts) attempts." -ForegroundColor Green
}
else {
Write-Host "File not found after $($global:Result.Attempts) attempts." -ForegroundColor Red
}
Update
As per your comments, I understand that the calling script should show the form (which does nothing more that show on screen) AND is responsible for closing it after the file has been found.
The code below should do what you ask by defining the $Form as a global variable and by using the .Show() method of the form instead of ShowDialog():
GUI.ps1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$global:Form = New-Object System.Windows.Forms.Form
$global:Form.ClientSize = '578,400'
$global:Form.Text = "Form"
$global:Form.BackColor = "#c1daf7"
$global:Form.WindowState = 'Maximized'
$global:Form.FormBorderStyle = "FixedDialog"
$global:Form.ControlBox = $false # hide sizing and close buttons
$global:Form.TopMost = $true
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$global:Form.Controls.AddRange(#($Label1,$Label2))
# don't use ShowDialog() here because it will block the calling script
$global:Form.Show()
the calling script
function Test-FileExists {
[CmdletBinding()]
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[string]$Path,
[string]$Pattern = '*.*'
)
$nAttempts = 1
$file = Get-ChildItem -Path $Path -Filter $Pattern -File | Select-Object -First 1
if (!$file) {
# show the GUI
& "D:\GUI.ps1"
do {
Start-Sleep -Seconds 1
$nAttempts++
Write-Verbose "Attempt No. $nAttempts"
$file = Get-ChildItem -Path $Path -Filter $Pattern -File | Select-Object -First 1
} until ($file)
# clean up the form
$global:Form.Dispose()
$global:Form = $null
}
Write-Verbose "File '$($file.FullName)' Exists. Found after $nAttempts attempt(s)."
return $true
}
$SN = "708TSTA"
$MAC = "2E5961370"
# call the function that can call the GUI.ps1 script
if (Test-FileExists -Path 'D:\SERVER\SHARE' -Pattern "*$SN-$MAC*" -Verbose) {
Write-Host "Found"
}
Hope that helps

Intermittent error (index into null) with detection of SelectedIndex change in Windows Forms

I'm getting an intermittent error with this method of changing a forms text label according to the selected item in a Listview box.
Example code as below, changing the entry will intermittently give:
Cannot index into a null array.
At C:\temp\test.ps1:62 char:5
+ $SelectedPath.Text = $VMsListBox.SelectedItems.SubItems[1].Text
# Import namespaces
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Demo'
$form.Size = '580,545'
$form.StartPosition = 'CenterScreen'
$form.FormBorderStyle = 'FixedSingle'
$form.MaximizeBox = $false
# Listview box to display found open files
$VMsListBox = New-Object System.Windows.Forms.ListView
$VMsListBox.View = [System.Windows.Forms.View]::Details
$VMsListBox.Location = '15,120'
$VMsListBox.size = '435,10'
$VMsListBox.Height = 250
$VMsListBox.Columns.Add('Name') | Out-Null
$VMsListBox.Columns.Add('Path') | Out-Null
$VMsListBox.FullRowSelect = $true
$VMsListBox.MultiSelect = $false
# Selected file label
$SelectedFnameLbl = New-Object System.Windows.Forms.Label
$SelectedFnameLbl.Location = '10,25'
$SelectedFnameLbl.Size = '80,19'
$SelectedFnameLbl.Text = 'File Name:'
# Selected file name
$SelectedFname = New-Object System.Windows.Forms.Label
$SelectedFname.Location = '100,25'
$SelectedFname.Size = '300,19'
$SelectedFname.Text = 'n/a'
$SelectedFname.AutoEllipsis = $true
# Path Label
$SelectedFileLbl = New-Object System.Windows.Forms.Label
$SelectedFileLbl.Location = '10,45'
$SelectedFileLbl.Size = '80,19'
$SelectedFileLbl.Text = 'File Path:'
# Selected filepath
$SelectedPath = New-Object System.Windows.Forms.Label
$SelectedPath.Location = '100,45'
$SelectedPath.Size = '300,19'
$SelectedPath.Text = 'n/a'
$SelectedPath.AutoEllipsis = $true
$form.Controls.AddRange(#($VMsListBox,$SelectedFileLbl,$SelectedPath,$SelectedFnameLbl,$SelectedFname))
# Populate ListView
$Files = Get-ChildItem -Path 'c:\temp' -File
$Files | ForEach-Object {
$Entry = New-Object System.Windows.Forms.ListViewItem($_.Name) -ErrorAction Stop
$Entry.SubItems.Add($_.FullName) | Out-Null
$VMsListBox.Items.Add($Entry) | Out-Null
}
$VMsListBox_SelectedIndexChanged={
$SelectedFname.Text = $VMsListBox.SelectedItems.Text
$SelectedPath.Text = $VMsListBox.SelectedItems.SubItems[1].Text
Write-Host "Entry changed"
}
$VMsListBox.Add_SelectedIndexChanged($VMsListBox_SelectedIndexChanged)
# Show form
$form.ShowDialog() | Out-Null
$form.Dispose()
Can anyone point me where I'm going wrong please? Or is there a better way of doing this
Ok, I found this
Apparently retained legacy behaviour and the fix is to check for null:
$VMsListBox_SelectedIndexChanged={
If($VMsListbox.SelectedItems -ne $Null){
$SelectedFname.Text = $VMsListBox.SelectedItems.Text
$SelectedPath.Text = $VMsListBox.SelectedItems.SubItems[1].Text
Write-Host "Entry changed"
}
}

Using a function to populate a textbox in Powershell

I currently have a script that queries our AD for the users' AD attribute "Department".
Right now, the script will run "successfully" but all output for Textbox2 goes to the console instead of TextBox2.
If I change my function Get-CCUsers to be a query without variables, like Get-ADUser -Filter "Department -eq 19330" (we use numbers for our departments) then the output shows in TextBox2 as I want it to.
How can I get my function to populate TextBox2?
BTW, this script was cobbled together with my limited understanding, and there may well be superfluous or nonsense lines in here.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Howard Center Profile Migration'
$form.Size = New-Object System.Drawing.Size(800,650)
$form.StartPosition = 'CenterScreen'
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Point(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = 'Cancel'
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $CancelButton
$form.Controls.Add($CancelButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = 'Please enter the Cost Center # in the space below:'
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(100,20)
$form.Controls.Add($textBox)
$RunButton = New-Object System.Windows.Forms.Button
$RunButton.Location = New-Object System.Drawing.Point(75,120)
$RunButton.Size = New-Object System.Drawing.Size(75,23)
$RunButton.Text = 'RUN'
#$RunButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
<#$RunButton.Add_Click({
#add here code triggered by the event
$TextBox2.Text = Get-Process | Format-Table -Property ProcessName, Id, CPU -AutoSize | Out-String
})
#>
$form.AcceptButton = $RunButton
$form.Controls.Add($RunButton)
$label2 = New-Object System.Windows.Forms.Label
$label2.Location = New-Object System.Drawing.Point(10,70)
$label2.Size = New-Object System.Drawing.Size(280,20)
$label2.Text = 'When you are ready click the Run button below'
$form.Controls.Add($label2)
$TextBox2 = New-Object system.windows.Forms.TextBox
$TextBox2.Text = ""
$TextBox2.Multiline = $true
$TextBox2.BackColor = "#013686"
$TextBox2.ScrollBars = "Both"
$TextBox2.Width = 750
$TextBox2.Height = 450
$TextBox2.location = new-object system.drawing.point(10,150)
$TextBox2.Font = "Microsoft Sans Serif,10"
$TextBox2.ForeColor = "#ffffff"
$Form.controls.Add($TextBox2)
$form.Topmost = $true
function Get-CCUsers {
Write-Host "The textbox text is $textbox.Text"
$dept = $textBox.Text
$deptUsers = Get-ADUser -Filter "Department -eq $dept"
ForEach ($user in $deptUsers) {
IF ( ((get-aduser $user).enabled) -eq $True ) {
$UHomeDir = (Get-ADUser $user -Properties HomeDirectory).HomeDirectory
$UProfPath = (Get-ADUser $user -Properties ProfilePath).ProfilePath
Write-Host "$user, $UHomeDir, $UProfPath"
}
}
}
$RunButton.Add_Click({
$TextBox2.Text = Get-CCUsers
})
$TextBox2.Add_TextChanged({
$TextBox2.SelectionStart = $TextBox2.Text.Length
$TextBox2.ScrollToCaret()
})
$form.Add_Shown({$Form.Update()})
$result = $form.ShowDialog()
$global:x = $textBox.Text
# $x
# if ($result -eq [System.Windows.Forms.DialogResult]::OK)
#{
#}
I have made your script working changing the 'Get-CCUSers' function
function Get-CCUsers {
Write-Host "The textbox text is $textbox.Text"
$dept = $textBox.Text
$deptUsers = Get-ADUser -Filter "Department -eq '$dept'"
$res = #()
ForEach ($user in $deptUsers) {
IF ( ((get-aduser $user).enabled) -eq $True ) {
$UHomeDir = (Get-ADUser $user -Properties HomeDirectory).HomeDirectory
$UProfPath = (Get-ADUser $user -Properties ProfilePath).ProfilePath
$res += "$user, $UHomeDir, $UProfPath"
}
}
return $res
}
In short:
I removed the Write-Host (the output was actually redirected to stdout)
I added a $res in the function initialized as array
In the ForEach loop, results are added as items in the array
The $res is returned by the function, and then used $RunButton

How Do I Access CheckBoxes From a Function

I've created a form and dynamically added CheckBoxes and CheckBox names. How can I programmatically check one particular CheckBox from the Get-LicenseDetails function? Missing bracket has been added.
import-module MSOnline
Function Get-LicenseDetails {
Param ($upn)
$licenses = Get-MsolUser -UserPrincipalName $upn
ForEach ($license in $licenses.Licenses) {
If ($license.AccountSkuId -like '*ENTERPRISEPACK') {
$serviceName = $serviceStatus.ServicePlan.ServiceName
$checkBox.Checked = $true
}
}
}
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$System_Drawing_Point = New-Object System.Drawing.Point
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object Windows.Forms.Form
$form.Text = "Office 365 Licensing"
$form.Name = "Form1"
$form.Size = New-Object Drawing.Size #(316, 510)
#SEARCH BUTTON
$searchBtn = New-Object System.Windows.Forms.Button
$System_Drawing_Point.X = 226
$System_Drawing_Point.Y = 38
$searchBtn.Location = $System_Drawing_Point
$searchBtn.add_click({Get-LicenseDetails "user.name#domain.com"})
$searchBtn.Size = New-Object System.Drawing.Size(67, 23)
$searchBtn.Text = "Click Me"
$form.Controls.Add($searchBtn)
#CHECKBOXES
$y = 80
$Services = (Get-MsolAccountSku | Where-Object {$_.SkuPartNumber -eq "ENTERPRISEPACK"}).ServiceStatus.ServicePlan.ServiceName
ForEach ($service in $Services) {
$checkbox = New-Object System.Windows.Forms.CheckBox
$checkbox.Text = $service
$checkbox.Name = "CheckBox_$service"
$checkbox.Size = New-Object System.Drawing.Size(260,17)
$checkbox.Location = New-Object System.Drawing.Size(10,$y)
$y += 25
$form.Controls.Add($checkbox)
}
$drc = $form.ShowDialog()
First of all, you are missing a square Bracket where you load the System.Drawing Assembly.
You could access the CheckBox in the Get-LicenseDetails bei either passing them to the function or by accessing them using $global:. However, I wouldn't pass GUI elements to that function. Instead I would create a model (new object) and pass that to the Get-LicenseDetails method.
OK, I've figured it out. I used these lines in the function:
$checkBoxName = "CheckBox_" + $serviceName
$checkbox = $form.Controls | Where-Object {$_.name -eq $checkBoxName}
$checkbox.Checked = $true

Resources