In PowerShell and Windows Forms, how to capture user-entered data using multiple dynamically created input controls - winforms

I have a script I need to use for multiple parameter data collection, as follows:
function Build-FormPanel($FormTitle){
Add-Type -Assembly System.Windows.Forms ## Load the Windows Forms assembly
## Create the main form
$form = New-Object Windows.Forms.Form
$form.FormBorderStyle = "FixedToolWindow"
$form.Text = $FormTitle
$form.AutoScroll = $True
$form.StartPosition = "CenterScreen"
$form.Width = 740 ; $form.Height = 480 # Make the form wider
#Add Buttons- ## Create the button panel to hold the OK and Cancel buttons
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size #(400,40)
$buttonPanel.Dock = "Bottom"
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 10; $cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Anchor = "Right"
## Create the OK button, which will anchor to the left of Cancel
$okButton = New-Object Windows.Forms.Button
$okButton.Top = $cancelButton.Top ; $okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Anchor = "Right"
## Add the buttons to the button panel
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
## Add the button panel to the form
$form.Controls.Add($buttonPanel)
## Set Default actions for the buttons
$form.AcceptButton = $okButton # ENTER = Ok
$form.CancelButton = $cancelButton # ESCAPE = Cancel
return $form
}
$LeftMargin = 25
$BottomMargin = 30
$i = 0
$form = Build-FormPanel "Please update server configurations"
foreach($param in $hash){#Where $hash is an "dictionary" of key/value pairs
$k = $param.Key
$v = $param.Value
$lblValue = New-Object System.Windows.Forms.Label
$lblValue.Text = $k+":"
$lblValue.Top = 20*$i ; $lblValue.Left = $LeftMargin; $lblValue.Width=150 ;$lblValue.AutoSize = $true
$form.Controls.Add($lblValue) # Add to Form
#
$txtValue = New-Object Windows.Forms.TextBox
$txtValue.Top = 20*$i; $txtValue.Left = 160; $txtValue.Width = 320;
$txtValue.Text = $v
$form.Controls.Add($txtValue) # Add to Form
$i++
}
$form.Topmost = $True
$form.Add_Shown( { $form.Activate(); } )
$result = $form.ShowDialog()
if($result -eq "OK")
{
$j = 0;
foreach($param in $hash){
${"txtValue_$j"}.Text
$j++
}
}
else {Write-Host "Cancel"}
Basically, this works OK to display the form and inputs. But after submission, I am unable to capture all the user inputs. Only the last input value is captured, obviously because the variables get overwritten in the loop.
How can I achieve capturing the data as described?

The issue as you have mentioned is because of the its getting overwritten.
I can give you a logical set off.
YOu can use it in a loop and in the loop , you store all the data either in array or if its dynamic then you can use arraylist by using
New-Object System.Collections.ArrayList
. But recommended is to create PSCustomObject , store in that and add that to the arraylist each time.
Finally you can get the output captured in the arraylist.
Further you can try making the arraylist Global so that it will be available for the entire script.
Hope it helps.

Related

PowerShell Version 4.0 (.PS1 File) - Add Text from TextBox into an Array to be printed on the console

Please Note this is PowerShell 4.0. I don't think Convert-String is available to , but granted, answers for other versions of PowerShell are still worth the read.
Alright. I'm very new to PowerShell, but interestingly enough both through Google and StackOverFlow I haven't been able to find the answer to this; I'm just not sure where to go yet.
Please keep in mind that i want to launch the file example.ps1 from the PowerShell prompt.
Function Show_Form
{
Add-Type -AssemblyName System.Windows.Forms
$global:Y_Position = 10
$global:X_Position = 5
$Form = New-Object system.Windows.Forms.Form
$Form.Text = "Sample Form"
$Form.Width = 400
$Form.Height = 450
$Form.AutoScroll = $True
$Form.AutoSize = $True
$Form.AutoSizeMode = "GrowOnly"
$Form.MinimizeBox = $False
$Form.MaximizeBox = $False
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "Please Fill in the Required Information:`n"
$Label.AutoSize = $True
$Form.Controls.Add($Label)
$LabelTextBox = New-Object System.Windows.Forms.Label
$LabelTextBox.Location = New-Object System.Drawing.Point(10,40)
$LabelTextBox.Text = $Text
$LabelTextBox.AutoSize = $True
$Form.Controls.Add($LabelTextBox)
$global:textBox = New-Object System.Windows.Forms.TextBox
$X_Position = $X_Position+90
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$Form.Controls.Add($textBox)
$Okbutton = New-Object System.Windows.Forms.Button
$Okbutton.Location = New-Object System.Drawing.Size(250,570)
$Okbutton.Size = New-Object System.Drawing.Size(100,30)
$Okbutton.Text = "OK"
<#-----------------------------------#>
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
<#-----------------------------------#>
$Okbutton.Add_Click({$Form.Close()})
$Form.Controls.Add($Okbutton)
$Form.ShowDialog()
}
Show_Form
My problem is with these lines:
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
The Add_Click({Write-Host $textBox.text}) displays in the Console but when I add $textBox.text to an array (object array or otherwise) or replace another element with it then print the Array, it is always just blank.
.
And if I do the following (Note: Arr[0] AND Arr[1])
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
$global:Arr[1] = "$textBox.text"
$OkButton.Add_Click({Write-Host $Arr})
Both elements get written as the value System.Windows.Forms.TextBox, Text: .text
I'm wondering how to make $textBox.text display in the Console in the array?
The problem you face is simple.
You assumed that $global:Arr[1] = "$textBox.text" would be set the actual text value from when you click the Ok button in your form.
But actually, this section of code is called before $Form.ShowDialog(). Therefore, at that point in time, your textbox is indeed empty, which explains what you get.
If you want your global array to be set to the textbox text after the Ok button is pressed, you need to set the array value when the user click the button through the Add_Click event.
Like this:
$OkButton.Add_Click( { $global:Arr[0] = $textBox.text })
Additional note
If you're not familiar already with how debugging work in Powershell, I'd recommend you to look at some debugging tutorials.
You would have immediately seen that the script did go to your variable assignment before the form is even shown, which would have possibly clued you in on the situation.
--
You can see below a debug point on the assignment line. The execution is stopped before I had a chance to see or enter anything in the form, which indicates that the value clearly can't be the one from the form. Querying $textBox.text at that point from the console shows the value to be empty, as we would expect from a newly initialized form.
Some documentation: how to debug scripts (even though it says ISE, all of this should be good with VSCode too)

How to add a timer in powershell to quit the form after certain amount of time [duplicate]

This question already has an answer here:
Powershell - Close form after time period
(1 answer)
Closed 3 years ago.
I have created a form (still in progress) in PowerShell that has radio buttons, which I want to display on the screen for a certain amount of time, and if that time has elapsed, it would send an email and close the form. It is not clear to me how to add the timer to the form, the rest I can figure it out.
$RadioButtonList = #( "Tom", "Dick", "Harry", "John", "Jane" )
$RadioButtonYMargin = 10
$RadioButtonIndex = 0
$RadioButtonX = 20
$RadioButtonY = (10 + $RadioButtonYMargin)
$RadioButtonYOffset = 30
$RadioButtonWidth = 350
$RadioButtonHeight = 20
$GroupBoxXMargin = 7
$GroupBoxX = 20
$GroupBoxY = 30
$GroupBoxWidth = 400
$GroupBoxHeight = $RadioButtonY + ( $RadioButtonList.Count * ( $RadioButtonHeight + 9 )) + $RadioButtonYMargin
$ButtonYMargin = 50
$ButtonY = $GroupBoxY + $GroupBoxHeight + $ButtonYMargin
$ButtonWidth = 100
$ButtonHeight = 40
$FormWidth = $GroupBoxWidth + (($GroupBoxX + $GroupBoxXMargin) * 2)
$FormHeight = $GroupBoxY + $GroupBoxHeight + $ButtonHeight + ($ButtonYMargin * 2)
$ButtonXSpacing = 50
$ButtonXMargin = [Int](($FormWidth - (($ButtonWidth * 2) + $ButtonXSpacing)) / 2)
Function RadioButtonClick ( $RadioButtonSelected ) {
$Form.AcceptButton.Enabled = $True
}
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
# Set the size of your form
$Form = New-Object System.Windows.Forms.Form
$Form.Width = $FormWidth
$Form.Height = $FormHeight
$Form.Text = "Operator, acknowledge your presence"
# Set the font of the text to be used within the form
$Font = New-Object System.Drawing.Font("Times New Roman",12)
$Form.Font = $Font
# Create a group that will contain your radio buttons
$GroupBox = New-Object System.Windows.Forms.GroupBox
$GroupBox.Location = New-Object System.Drawing.Size( $GroupBoxX, $GroupBoxY )
$GroupBox.size = New-Object System.Drawing.Size( $GroupBoxWidth, $GroupBoxHeight )
$GroupBox.text = "Please select your name below:"
While ( $RadioButtonIndex -lt $RadioButtonList.Count ) {
$RadioButton = New-Object System.Windows.Forms.RadioButton
$RadioButton.Location = New-Object System.Drawing.Size( $RadioButtonX, $RadioButtonY )
$RadioButton.Size = New-Object System.Drawing.Size( $RadioButtonWidth, $RadioButtonHeight )
$RadioButton.Checked = $False
$RadioButton.Text = $RadioButtonList[ $RadioButtonIndex ]
$RadioButtonY += $RadioButtonYOffset
$RadioButton.Add_Click({ RadioButtonClick $This.Text })
$GroupBox.Controls.Add( $RadioButton )
$RadioButtonIndex += 1
}
# Add an OK button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size( $ButtonXMargin, $ButtonY )
$OKButton.Size = New-Object System.Drawing.Size( $ButtonWidth, $ButtonHeight )
$OKButton.Text = 'OK'
$OKButton.Enabled = $False
$OKButton.DialogResult=[System.Windows.Forms.DialogResult]::OK
# Add a cancel button
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size( ($ButtonXMargin + $ButtonWidth + $ButtonXSpacing), $ButtonY )
$CancelButton.Size = New-Object System.Drawing.Size( $ButtonWidth, $ButtonHeight )
$CancelButton.Text = "Cancel"
$CancelButton.DialogResult=[System.Windows.Forms.DialogResult]::Cancel
# Add all the Form controls on one line
$Form.Controls.AddRange(#($GroupBox,$OKButton,$CancelButton))
# Assign the Accept and Cancel options in the form to the corresponding buttons
$Form.AcceptButton = $OKButton
$Form.CancelButton = $CancelButton
# Activate the form
$Form.Add_Shown({$Form.Activate()})
# Get the results from the button click
$dialogResult = $Form.ShowDialog()
# If the OK button is selected
if ($dialogResult -eq "OK") {
$SelectedRadioButton = ($GroupBox.Controls | Where-Object{$_.Checked}).Text
Write-Host "Selection was $SelectedRadioButton"
} Else {
Write-Host "Cancelled"
}
Here's a minimal example that shows how to use a timer to automatically close a WinForms form after 2 seconds (PSv5+ syntax):
using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
# Create the form.
$form = [Form]::new()
# Create a timer.
$timer = [Timer]::new()
$timer.Interval = 2000 # 2 seconds
# Set up the event handler for the timer.
$timer.Add_Tick({
# Close the form.
# Note: We dispose (stop) the timer later, right after the form closes;
# with a very short interval, another tick event could fire before
# that happens, but calling .Close() on an already closed form is fine.
$form.Close()
})
# Start the timer.
$timer.Start()
# Show the dialog, which will automatically close
# when the timer fires.
$result = $form.ShowDialog()
# Dispose (stop) the timer. Doing this here instead of in the tick
# event handler ensures that the timer is stopped even when the form
# was closed by the user.
$timer.Dispose()
# Dispose the form and its controls. Skip, if you want to redisplay the form later.
$form.Dispose()
Note the need for $form.Dispose(), because the form was shown as a modal dialog with .ShowDialog() (rather than .Show()), in which case closing it does not implicitly dispose it - see this answer for details.
PSv4- syntax (where using namespace and ::new() aren't available):
Add-Type -AssemblyName System.Windows.Forms
# Create the form.
$form = New-Object System.Windows.Forms.Form
# Create a timer.
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 2000 # 2 seconds
# Set up the event handler for the timer.
$timer.Add_Tick({
# Close the form.
# Note: We dispose (stop) the timer later, right after the form closes;
# with a very short interval, another tick event could fire before
# that happens, but calling .Close() on an already closed form is fine.
$form.Close()
})
# Start the timer.
$timer.Start()
# Show the dialog, which will automatically close
# when the timer fires.
$result = $form.ShowDialog()
# Dispose (stop) the timer. Doing this here instead of in the tick
# event handler ensures that the timer is stopped even when the form
# was closed by the user.
$timer.Dispose()
# Dispose the form and its controls. Skip, if you want to redisplay the form later.
$form.Dispose()

How Can I change the color of the Text on TabPage?

What properties need to be applied to change the forecolor and backcolor of the text on Tabpage?
See Picture:
https://imgur.com/a/Su8aSg7
Here is my Code:
$TabControl_Main = New-Object System.Windows.Forms.TabControl
$TabControl_Main.Location = New-Object System.Drawing.Size(20,550)
$TabControl_Main.Size = New-Object System.Drawing.Size(850,270)
$form_MainForm.Controls.Add($TabControl_Main)
$TabPage1 = New-Object System.Windows.Forms.TabPage
$TabPage1.Location = New-Object System.Drawing.Size(20,550)
$TabPage1.Size = New-Object System.Drawing.Size(850,270)
$TabPage1.Text = "Processes"
$TabControl_Main.Controls.Add($TabPage1)
You have to create an event and draw the area. Here is some code based on this example in c#, credits #Fun Mun Pieng.
# assign a color for each tab
$PageColor = #{0 = "lightgreen";
1 = "yellow";
2 = "lightblue"}
# define the event
$tabControl_Drawing = {
param([object]$Sender, [System.EventArgs]$e)
$Background = new-object Drawing.SolidBrush $PageColor[$e.Index]
$Foreground = new-object Drawing.SolidBrush black
$tabGraphics = $e.Graphics
$tabBounds = $e.Bounds
$tabGraphics.FillRectangle($Background,$tabBounds)
$tabTextSize = $tabGraphics.MeasureString($sender.TabPages[$e.Index].text, $e.Font)
$tabGraphics.DrawString($Sender.TabPages[$e.Index].Text,$e.Font,$Foreground,$tabBounds.Left + ($tabBounds.Width - $tabTextSize.Width) / 2,$tabBounds.Top + ($tabBounds.Height -$tabTextSize.Height) / 2 +1)
$e.DrawFocusRectangle()
}
# add the event
$TabControl_Main.add_DrawItem($tabControl_Drawing)
A little easier to use is HotTrack:
$TabControl_Main.HotTrack = $true
You will see the effect when you execute your script with powershell instead of powershell ISE.
BackColor does nothing. To use the words of MSDN:
BackColor > This member is not meaningful for this control.
edit: added the code.

Powershell Timer without pausing form

Good day all
As usual I am stuck, I have a simple script that is designed to show you the status of a list of systems bitlocker. Give it a txt of system names, it does the rest. All works as intended; however its updating the list on a ticking Timer, which when executing will make the window unresponsive and appear to be broken (to those users who dont understand what its doing). Is there a way to branch this off in some fashion to avoid this hangingup?
I considered doing a branch but I do now know how to make that branch update an object in its parent... if thats even possible.
CODE:
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' )
$d = New-Object Windows.Forms.OpenFileDialog
$d.ShowHelp = $true
$d.filter = "System ID List (*.txt)| *.txt"
$result = $d.ShowDialog( )
$names = #()
$names = Get-Content $d.filename
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$myWindow = new-object System.Windows.Forms.form
$myDataGrid = new-object System.windows.forms.DataGridView
$myDataGrid.Location = new-object System.Drawing.Size(20,30)
$myDataGrid.size = new-object System.Drawing.Size(450,480)
$myDataGrid.AllowUserToAddRows = $False
$myDataGrid.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnsMode]::Fill
$myDataGrid.RowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Bisque
$myDataGrid.AlternatingRowsDefaultCellStyle.BackColor = [System.Drawing.Color]::Beige
$myDataGrid.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D
$myDataGrid.ColumnHeadersDefaultCellSTyle.ForeColor = [System.Drawing.Color]::Maroon
$myDataGrid.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid.RowHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::Tan
$myDataGrid.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::AutoSize
$myWindow.Controls.Add($myDataGrid)
# Define menus
$myMenuStrip = new-object System.Windows.Forms.MenuStrip
$FileExit = new-object System.Windows.Forms.ToolStripMenuItem("&Exit")
$FileExit.add_Click({ $myWindow.close() })
$myMenuStrip.Items.Add($FileMenu)
$myWindow.Controls.Add($myMenuStrip)
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.add_tick({
$dataTable = New-Object System.Data.DataTable
$dataTable.Columns.Add("System") | Out-Null
$dataTable.Columns.Add("BitLocker % (C:)") | Out-Null
foreach ($name in $names) {
$stat = (manage-bde.exe -cn $name -status C:)[11].split(":")[1]
$row = $dataTable.NewRow()
$row["System"] = $name
$row["BitLocker % (C:)"] = $stat
$dataTable.Rows.Add($row)
}
$myDataGrid.DataSource = $dataTable
})
# main program body
$myWindow.Text = "BitLocker Status"
$myWindow.size = new-object System.Drawing.Size(500,600)
$myWindow.autoscroll = $true
$myWindow.Add_Shown({$myWindow.Activate()})
$timer.Start()
$myWindow.ShowDialog()
I cannot believe nobody answered this, perhaps I wasn't clear.
Eitherway the solution was easy, write-output $object, then receive-job.
Done

Powershell: Selecting DataGridView Row

So far I have this code.
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(900,600)
$dataGridView = New-Object System.Windows.Forms.DataGridView
$dataGridView.Size=New-Object System.Drawing.Size(800,400)
$go = New-Object System.Windows.Forms.Button
$go.Location = New-Object System.Drawing.Size(300,450)
$go.Size = New-Object System.Drawing.Size(75,23)
$go.text = "Select"
$form.Controls.Add($go)
$form.Controls.Add($dataGridView)
$dataGridView.ColumnCount = 4
$dataGridView.ColumnHeadersVisible = $true
$dataGridView.Columns[0].Name = "Name"
$dataGridView.Columns[1].Name = "ID"
$dataGridView.Columns[2].Name = "Description"
$dataGridView.Columns[3].Name = "Memory"
$dataGridView.Columns[0].width = 240
get-process | foreach {
$dataGridView.Rows.Add($_.Name,$_.ID,$_.Description,$_.WorkingSet) | out-null
}
$go.Add_Click({
$selectedRow = $dataGridView.CurrentRowIndex
write-host $selectedRow
})
[void]$form.ShowDialog()
It simply gets the Process Name, ID, etc. properties and puts them into pre-defined headers in a DataGridView.
My problem is that I want to see the row I've clicked on via $selectedRow = $dataGridView.CurrentRowIndex and output it to the console. Instead, when the 'Select' button is pushed, a blank string is output to the terminal.
You can also get the row index with:
$dataGridView.CurrentCell.RowIndex
or
$dataGridView.SelectedRows[0].Index
You may also want to set the grid MultiSelect property to $false. Currently it allows multiple rows selection. Another thing to consider is setting the SelectionMode property to 'FullRowSelect'. When the grid is populated the first column is selected, not the whole row.
Change
$selectedRow = $dataGridView.CurrentRowIndex
to
$selectedRow = $dataGridView.CurrentRow.Index

Resources