PowerShell GUI - Setting "button.Add_MouseEnter" actions in a function - powershell

I'm creating a small PowerShell GUI with a buttons to quickly run various powershell commands. I've got the general layout how I want it, but now I'm trying to simplify my code by using a button building function.
I don't have an issue setting assorted traits like "Button.Text", "Button.SetToolTip", or "Button.Add_Click" in a separate function, but I'm having issues setting the "Button.Add_MouseEnter" and "Button.Add_MouseLeave" aspects in a stand-alone function. The code compiles fine, but when I hover over the button in the gui I get the following error (similar errors for lines 8-10)
"The property 'BackColor' cannot be found on this object. Verify that the property exists and can be set.
At line:7 char:30
$tButton.Add_MouseEnter({$tButton.BackColor = '#990000'})
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : PropertyNotFound"
I tried creating the button object fully in the function and returning the button, which would set all the button traits correctly but still gives the same error when the mouse passes over. I think I may need to pass the button object to the function/return the button differently (depending on which code 1st or 2nd), but I don't know. I thought perhaps binding the parameters with [CmdletBinding()] but the error still comes up (I probably don't understand that as well as I should) Any insight would be appreciated
===== Initial Code =====
`
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
Clear-Host
function Set-Hover ($tButton){
$tButton.Add_MouseEnter({$tButton.BackColor = '#990000'})
$tButton.Add_MouseEnter({$tButton.ForeColor = '#000000'})
$tButton.Add_MouseLeave({$tButton.BackColor = '#000000'})
$tButton.Add_MouseLeave({$tButton.ForeColor = '#FFFFFF'})
}
$Window = New-Object System.Windows.Forms.Form
$Window.ClientSize = New-Object System.Drawing.Size(100, 50)
$Global:Button = New-Object 'system.Windows.Forms.Button'
$Button.Size = New-Object System.Drawing.Point(75,40)
$Button.Location = New-Object System.Drawing.Point(5, 5)
$Button.text = "Button"
$Button.BackColor = '#000000'
$Button.ForeColor = '#FFFFFF'
Set-Hover -tButton $Button
$Window.Controls.Add($Button)
<# The code works when the Mouse_Enter/Leave lines are here, not in a function
$Button.Add_MouseEnter({$Button.BackColor = '#990000'})
$Button.Add_MouseEnter({$Button.ForeColor = '#000000'})
$Button.Add_MouseLeave({$Button.BackColor = '#000000'})
$Button.Add_MouseLeave({$Button.ForeColor = '#FFFFFF'})
#>
$Window.ShowDialog()
`
===== Second Code =====
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
Clear-Host
function Set-Hover {
$tButton = New-Object 'system.Windows.Forms.Button'
$tButton.Size = New-Object System.Drawing.Point(75,40)
$tButton.Location = New-Object System.Drawing.Point(5, 5)
$tButton.text = "Text"
$tButton.BackColor = '#FFFFFF'
$tButton.ForeColor = '#000000'
$tButton.Add_MouseEnter({$tButton.BackColor = '#990000'})
$tButton.Add_MouseEnter({$tButton.ForeColor = '#000000'})
$tButton.Add_MouseLeave({$tButton.BackColor = '#000000'})
$tButton.Add_MouseLeave({$tButton.ForeColor = '#FFFFFF'})
$tButton
}
$Window = New-Object System.Windows.Forms.Form
$Window.ClientSize = New-Object System.Drawing.Size(100, 50)
$Button1 = Set-Hover
$Window.Controls.Add($Button1)
<# The code works when the Mouse_Enter/Leave lines are here, not in a function
$Button.Add_MouseEnter({$Button.BackColor = '#990000'})
$Button.Add_MouseEnter({$Button.ForeColor = '#000000'})
$Button.Add_MouseLeave({$Button.BackColor = '#000000'})
$Button.Add_MouseLeave({$Button.ForeColor = '#FFFFFF'})
#>
$Window.ShowDialog()

You need to define the button in the form code, not create a new one inside the function when the mouse hovers over it (or away from it).
If you do want to use a function for this, change your function to accept parameters (the first is the button object itself, the second a switch to determine if it is an MouseEnter or MouseLeave event)
Also, better not use the old [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") syntax anymore.
Here's your code revised:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Set-Hover {
param (
[System.Windows.Forms.Button]$ButtonObject,
[switch] $MouseEnter
)
if ($MouseEnter) {
$bkColor = '#990000'
$fgColor = '#000000'
}
else {
$bkColor = '#000000'
$fgColor = '#FFFFFF'
}
$ButtonObject.BackColor = $bkColor
$ButtonObject.ForeColor = $fgColor
}
$Window = New-Object System.Windows.Forms.Form
$Window.Size = New-Object System.Drawing.Size(250, 250)
$Button = New-Object System.Windows.Forms.Button
$Button.Size = New-Object System.Drawing.Point(75,40)
$Button.Location = New-Object System.Drawing.Point(5, 5)
$Button.Text = "Button"
$Button.BackColor = '#000000'
$Button.ForeColor = '#FFFFFF'
# inside an event handler, you can refer to the current object with keyword $this
$Button.Add_MouseEnter({ Set-Hover -ButtonObject $this -MouseEnter })
$Button.Add_MouseLeave({ Set-Hover -ButtonObject $this })
$Window.Controls.Add($Button)
$Window.ShowDialog()
# important: Remove the form from memory when done with it
$Window.Dispose()

Related

Powershell GUI strip menu item OnClick event

I am practicing building a GUI for an application I built and am struggling a little bit with stripMenus. For my 3rd menu item I am trying to list my socials and OnClick have it open up youtube for example. I am just not completely familiar with the syntax of it and it is shockingly hard to find documentation regarding this online.
Add-Type -AssemblyName System.Windows.Forms
$form=New-Object System.Windows.Forms.Form
$form.StartPosition='CenterScreen'
$MenuBar = New-Object System.Windows.Forms.MenuStrip
$fileToolStripMenuItem = new-object System.Windows.Forms.ToolStripMenuItem
$editionToolStripMenuItem = new-object System.Windows.Forms.ToolStripMenuItem
$socialToolStripMenuItem = new-object System.Windows.Forms.ToolStripMenuItem
$YtToolStripMenuItem = new-object System.Windows.Forms.ToolStripMenuItem
$Form.Controls.Add($MenuBar)
$MenuBar.Items.AddRange(#(
$fileToolStripMenuItem,
$editionToolStripMenuItem,
$socialToolStripMenuItem))
$fileToolStripMenuItem.Name = "fileToolStripMenuItem"
$fileToolStripMenuItem.Size = new-object System.Drawing.Size(35, 20)
$fileToolStripMenuItem.Text = "&File"
$editionToolStripMenuItem.Name = "editionToolStripMenuItem"
$editionToolStripMenuItem.Size = new-object System.Drawing.Size(51, 20)
$editionToolStripMenuItem.Text = "&Edition"
$socialToolStripMenuItem.DropDownItems.AddRange(#($YtToolStripMenuItem))
$socialToolStripMenuItem.Name = "socialToolStripMenuItem"
$socialToolStripMenuItem.Size = new-object System.Drawing.Size(67, 20)
$socialToolStripMenuItem.Text = "&Socials"
$YtToolStripMenuItem.Name = "YtToolStripMenuItem"
$YtToolStripMenuItem.Size = new-object System.Drawing.Size(152, 22)
$YtToolStripMenuItem.Text = "&YouTube"
Below is where I would like to learn the syntax to execute powershell commands
function OnClick_YtToolStripMenuItem($Sender,$e){
#powershell -w h -NoP -NonI -Exec Bypass Start-Process https://www.youtube.com"
[void][System.Windows.Forms.MessageBox]::Show("Subscribe to my youtube")
}
$YtToolStripMenuItem.Add_Click( { OnClick_YtToolStripMenuItem $YtToolStripMenuItem $EventArgs} )
$form.ShowDialog()
I just needed to get rid of the event args as they were not needed. The following snippet is how I was able to get it working:
$YtToolStripMenuItem.Name = "YtToolStripMenuItem"
$YtToolStripMenuItem.Size = new-object System.Drawing.Size(152, 22)
$YtToolStripMenuItem.Text = "&YouTube"
function OnClick_YtToolStripMenuItem() {
Start-Process 'https://www.youtube.com/iamjakoby?sub_confirmation=1'
}
$YtToolStripMenuItem.Add_Click({ OnClick_YtToolStripMenuItem })

How to ensure Forms are the top window

I have a PS script that implements System.Windows.Forms in order to query technicians for some data.
I create the forms and set both .Topmost and .TopLevel to true in an attempt to have them show up over the Powershell window, but they continue to (for some reason inconsistently) appear behind the Powershell window. This slows down the process and is confusing in its inconsistency.
If anyone knows how to ensure these windows stay top without a mountain of code larger than the script itself that would be incredibly useful. I'll include the code I use to build one of the basic forms below.
Any simple solution that will allow these Forms to appear over the Powershell window is appreciated. It could even just minimize the PS window, but I don't want to launch without the window as we need it open. Thanks.
$form.Text = 'Computer Name Entry'
$form.Size = New-Object System.Drawing.Size(550,400)
$form.StartPosition = 'CenterScreen'
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Point(75,300)
$okButton.Size = New-Object System.Drawing.Size(75,23)
$okButton.Text = 'OK'
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $okButton
$form.Controls.Add($okButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(400,40)
$label.Text = 'Text is here:'
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,70)
$textBox.Size = New-Object System.Drawing.Size(400,20)
$form.Controls.Add($textBox)
$form.Topmost = $true
$form.TopLevel = $true
$form.Add_Shown({$textBox.Select()})
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
Do-Stuff
}
The easiest way I know of forcing the form to be topmost is to open it with a new temporary form that is TopMost as parameter for ShowDialog().
First, from your code remove the lines $form.Topmost = $true and $form.TopLevel = $true
Next, show your form like this:
# force the dialog TopMost by creating a temporary parent window for this form
$result = $form.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true }))
Another way of doing this is to use a piece of C# to return a windowhandle which implements the IWin32Window interface.
Then use this handle as the owner window for this form in the .ShowDialog() method of the form.
For this method, also remove the lines $form.Topmost = $true and $form.TopLevel = $true from your original code.
$iWin32Code = #"
using System;
using System.Windows.Forms;
public class Win32Window : IWin32Window {
public Win32Window(IntPtr handle) {
Handle = handle;
}
public IntPtr Handle { get; private set; }
}
"#
if (-not ([System.Management.Automation.PSTypeName]'Win32Window').Type) {
Add-Type -TypeDefinition $iWin32Code -ReferencedAssemblies System.Windows.Forms.dll
}
Now, using that code, create a handle for the currently running PowerShell process
# get the owner handle from this PowerShell process
$ownerHandle = New-Object Win32Window -ArgumentList ([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# and use that in the ShowDialog method as argument
$result = $form.ShowDialog($ownerHandle)
P.S. do not forget to clear your form from memory after you are done with it by calling
$form.Dispose()

Powershell output in color

I am using a Powershell form to output some data and I am wondering how I can get the output in color?
I am not using write-host. That is not what I am looking for. I know you can use -ForegroundColor for that.
It's for Get-ADUser -Filter "UserPrincipalName -like 'Username'" | Select Enabled
If output is False it needs to be in Red. If output is true just regular color.
Anyone who can help me?
Many thanks.
Ralph.
A follow-up to my comment
#region Begin functions and code behind
function RunCode {
$ProcessList = (Get-Process).Name
If ($ProcessList -ge 10)
{$DataSet.ForeColor = 'red'}
else {$DataSet.ForeColor = 'black'}
[void] $DataSet.Items.Addrange($ProcessList)
}
#endregion End functions and code behind
#region Begin GUI code
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '511,501'
$Form.text = "Form"
$Form.TopMost = $false
$RunCode = New-Object system.Windows.Forms.Button
$RunCode.text = "RunCode"
$RunCode.width = 90
$RunCode.height = 30
$RunCode.location = New-Object System.Drawing.Point(19,17)
$RunCode.Font = 'Microsoft Sans Serif,10'
$DataSet = New-Object system.Windows.Forms.ListBox
$DataSet.text = "listBox"
$DataSet.width = 204
$DataSet.height = 144
$DataSet.location = New-Object System.Drawing.Point(17,98)
$Form.controls.AddRange(#(
$RunCode,
$DataSet
))
$RunCode.Add_Click({ RunCode })
#endregion Begin GUI code
# Call the GUI
[void]$Form.ShowDialog()

Powershell multiple dynamic buttons

I built a dynamic Powershell GUI but i have trouble getting my buttons to work correctly.
I created a function to add texboxes and buttons. However they do not correspond correctly at this point. because i'm not sure how to bind the add_click to a specific textbox.
Here is the example code:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$Form = New-Object System.Windows.Forms.Form
$Form.Size = New-Object System.Drawing.Size(300,300)
$ButtonAdd = New-Object System.Windows.Forms.Button
$ButtonAdd.Location = New-Object System.Drawing.Point(20,20)
$ButtonAdd.Size = New-Object System.Drawing.Size(50,30)
$ButtonAdd.Text = 'Add'
$Form.Controls.Add($ButtonAdd)
$global:Counter = 0
$global:ButtonPosY = 20
$global:DefaultTextValue = ""
$ButtonAdd.Add_Click{Add-Fields}
Function Create-Button {
param ( $ButtonPosY, $TextBoxPosY)
$TextBoxField = New-Object System.Windows.Forms.TextBox
$TextBoxField.Location = New-Object System.Drawing.Point(181,$TextBoxPosY)
$TextBoxField.Size = New-Object System.Drawing.Size(50,30)
$TextBoxField.Text = $global:DefaultTextValue
New-Variable -name TextBoxField$global:Counter -Value $TextBoxField -Scope Global -Force
$Form.controls.Add($((Get-Variable -name TextBoxField$global:Counter).value))
$ButtonClick = New-Object System.Windows.Forms.Button
$ButtonClick.Location = New-Object System.Drawing.Point(100,$global:ButtonPosY)
$ButtonClick.Size = New-Object System.Drawing.Size(50,30)
$ButtonClick.Text = 'Click'
New-Variable -name ButtonClick$global:Counter -Value $ButtonClick -Scope Global -Force
$Form.controls.Add($((Get-Variable -name ButtonClick$global:Counter).value))
$ButtonClick.Add_Click({
$((Get-Variable -name TextBoxField$global:Counter -Scope Global).value).Text = 'hello'
})
}
Function Add-Fields {
$global:Counter = $global:Counter + 1
$global:ButtonPosY = $global:ButtonPosY + 40
Create-Button -TextBoxPosY $global:ButtonPosY -ButtonPosY $global:ButtonPosY
}
Create-Button -TextBoxPosY 21 -ButtonPosY 20
$Form.ShowDialog()
If the click button is pressed each time after new input is added everything works fine. however if multiple input fields are added first and then the click button is pressed the code breaks.
The Problem is here:
$ButtonClick.Add_Click({...})
I don't know how to add the counter (in my case $global:Counter) to the $ButtonClick variable as in $ButtonClick0, $ButtonClick1, ... etc. So right now when i add more buttons by calling the function the input will always be applied to the last added textbox since the add_click is not linked to a individual $ButtonClick0 variable.
How would this be done right?
After some serious reading i figured it out.
A name needs to be added to $ButtonClick
$ButtonClick.Name = $global:Counter
And the Add_Click event should look the following:
$ButtonClick.Add_Click({
[System.Object]$Sender = $args[0]
$((Get-Variable -name ('TextBoxField' + [int]$Sender.Name)).value).Text = 'hello'
})
However i don't know if this is how it should be done. Please post your solution if it looks different.

How do I properly use a function in a do/while loop?

I have created a function that creates a specific UI element with text input forms. This form also has three buttons:
One is supposed to display the text inputs again to do the same function again.
Another is supposed to finish and close the UI, passing the text input into the variables.
The last is supposed to cancel the UI element, doing nothing with anything in the text input forms.
Now I know the loop in the code isn't really complete, but I am having issues with it even performing the loop as well as passing the text forms into the variables. I know its something I am doing but it seems correct to me when I look at it.
Changed from an if loop, a while loop, and now a do/while loop.
Changed the position of the variables between the do section into the while section. Same for the if and while loops.
do {
ChangeDesc
}
while ($result -eq [System.Windows.Forms.DialogResult]::Retry)
{
$PC = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $HNInputBox.Text
$PC.Description = $DescInputBox.Text
$PC.Put()
}
ChangeDesc is the name of the function and works just as intended.
Expected to work is to loop the function 'ChangeDesc', and then when the 'Retry' or 'Ok' button is pressed, pass those forms to the variables as shown.
Currently, it will display the form, and when the 'Retry' button is pressed, the forms are passed properly and then the UI is closed out, the 'Ok' button does not pass any input and the 'Cancel' does the same thing.
Below is the rest of my lines of code for clarification.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function ChangeDesc {
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Data Entry Form'
$form.Size = New-Object System.Drawing.Size(300,210)
$form.StartPosition = 'CenterScreen'
$AnotherButton = New-Object System.Windows.Forms.Button
$AnotherButton.Location = New-Object System.Drawing.Point(15,130)
$AnotherButton.Size = New-Object System.Drawing.Size(75,23)
$AnotherButton.Text = 'Another?'
$AnotherButton.DialogResult = [System.Windows.Forms.DialogResult]::Retry
$form.AcceptButton = $AnotherButton
$form.Controls.Add($AnotherButton)
$FinishedButton = New-Object System.Windows.Forms.Button
$FinishedButton.Location = New-Object System.Drawing.Point(100,130)
$FinishedButton.Size = New-Object System.Drawing.Size(75,23)
$FinishedButton.Text = 'Finished'
$FinishedButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.CancelButton = $FinishedButton
$form.Controls.Add($FinishedButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Point(185,130)
$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)
$HNLabel = New-Object System.Windows.Forms.Label
$HNLabel.Location = New-Object System.Drawing.Point(10,20)
$HNLabel.Size = New-Object System.Drawing.Size(280,20)
$HNLabel.Text = 'Enter Host Name:'
$form.Controls.Add($HNLabel)
$HNInputBox = New-Object System.Windows.Forms.TextBox
$HNInputBox.Location = New-Object System.Drawing.Point(10,40)
$HNInputBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($HNInputBox)
$DescLabel = New-Object System.Windows.Forms.Label
$DescLabel.Location = New-Object System.Drawing.Point(10,70)
$DescLabel.Size = New-Object System.Drawing.Size(280,20)
$DescLabel.Text = 'Enter Description:'
$form.Controls.Add($DescLabel)
$DescInputBox = New-Object System.Windows.Forms.TextBox
$DescInputBox.Location = New-Object System.Drawing.Point(10,90)
$DescInputBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($DescInputBox)
$form.Topmost = $true
$form.Add_Shown({$HNInputBox.Select()})
$result = $form.ShowDialog()
}
Your form is already closed when the loop terminates, and the variables you're trying to use are local to your function. Assign the values you're trying to use to script- or global-scope variables at the end of the function, and the code should do what you expect:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function ChangeDesc {
$form = New-Object Windows.Forms.Form
...
$script:result = $form.ShowDialog()
$script:hostname = $HNInputBox.Text
$script:description = $DescInputBox.Text
}
do {
ChangeDesc
} while ($script:result -eq [Windows.Forms.DialogResult]::Retry)
$PC = Get-WmiObject Win32_OperatingSystem -Computer $script:hostname
$PC.Description = $script:description
$PC.Put()