Using the Microsoft Technet example here and pasted below
https://technet.microsoft.com/en-us/library/ff730941.aspx?f=255&MSPPError=-2147217396
I cannot get it to actually assign the user input to the variable $x
I've tried removing the void from this line at the end [void] $objForm.ShowDialog() but it appears to always return the Cancel text
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Data Entry Form"
$objForm.Size = New-Object System.Drawing.Size(300,200)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$x=$objTextBox.Text;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$objForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$x=$objTextBox.Text;$objForm.Close()})
$objForm.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please enter the information in the space below:"
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,40)
$objTextBox.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($objTextBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
$x
This code worked fine in PowerShell 1 and 2, but stopped working in PowerShell 3. PowerShell 3 offered a small set of syntax improvements and the usual new cmdlets, but on a less publicized note, it was rewritten on top of the Dynamic Language Runtime, a natural choice for a scripting language.
The move to the DLR changed the scoping rules in a way I don't think is well documented even to this day. Specifically, in a script block (like the one used to create a delegate) assigning to a variable creates a new variable in the scope of the block. This makes sense from the point of view of modularity, but isn't so obvious in a scripting language. In particular, consider the following:
$x = 2
&{
Write-Host $x;
$x = 3; Write-Host $x;
Remove-Variable "x"; Write-Host $x
$x = 3;
}
Write-Host $x
This prints:
2
3
2
2
Within the block, x takes its value from the parent scope, but when we assign it, we're actually creating a new variable in the private scope. When we remove that variable, any references to x are from the parent scope again. The assignment to x does not affect the value in the parent scope, as we can see from the Write-Host outside the block.
This explains why assigning a variable in a delegate "doesn't work": you're actually assigning to a new variable inside the delegate, the value of which is lost when the delegate ends. To assign to the variable in the parent scope, an explicit qualifier is necessary ($script:x or $global:x).
Related
I'm trying to make a GUI for an easy tool I need to check if a specific certificate is included in the file and Output the current and next line. This already works.
If I set the variables $INT1 and $Serial1 manually within the code it also works as intended.
e.g. $INT1=31 and $Serial1="5AA61E07726DAC13".
I always get an Error that the variable is empty when inputing the same values into the GUI and clicking OK. What am I doing wrong?
The code:
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.StartPosition = "CenterScreen"
$objForm.Size = New-Object System.Drawing.Size(400,250)
$objForm.Topmost = $True
#Label+Textbox first question
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,30)
$objLabel.Size = New-Object System.Drawing.Size(800,20)
$objLabel.Text = "INT of certificate"
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,50)
$objTextBox.Size = New-Object System.Drawing.Size(200,20)
$objForm.Controls.Add($objTextBox)
#Label+Textbox second question
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(10,130)
$objLabel2.Size = New-Object System.Drawing.Size(800,20)
$objLabel2.Text = "Serialnumber of the certificate"
$objForm.Controls.Add($objLabel2)
$objTextBox2 = New-Object System.Windows.Forms.TextBox
$objTextBox2.Location = New-Object System.Drawing.Size(10,150)
$objTextBox2.Size = New-Object System.Drawing.Size(200,20)
$objForm.Controls.Add($objTextBox2)
$INT1 = $objTextBox.Text;
$Serial1 = $objTextBox2.Text;
#OK Button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(100,175)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Name = "OK"
#$OKButton.DialogResult = "OK"
$OKButton.Add_Click({Invoke-WebRequest -Uri http://CENSORED.crl -OutFile crl.crl;
$test=certutil.exe -dump crl.crl |Out-String -stream | Select-String -pattern "$Serial1" -Context 0,1
[void] [Windows.Forms.MessageBox]::Show($test)})
$objForm.Controls.Add($OKButton)
[void] $objForm.ShowDialog()
Thank you!
Here's a rewrite of your code.
I'm assuming that you want variables $INT1 and $Serial1 to be available after the windows closes.
In that case, fill them inside the Click() scriptblock and refer to them using $script: scoping, otherwise they are just new variables local to the scriptblock.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# these variables need to visible inside the Click scriptblock of the button
# and be also available after the windows closes
$INT1 = $null
$Serial1 = $null
$objForm = New-Object System.Windows.Forms.Form
$objForm.StartPosition = "CenterScreen"
$objForm.Size = New-Object System.Drawing.Size(400,250)
$objForm.Topmost = $True
#Label+Textbox first question
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,30)
$objLabel.Size = New-Object System.Drawing.Size(800,20)
$objLabel.Text = "INT of certificate"
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,50)
$objTextBox.Size = New-Object System.Drawing.Size(200,20)
$objForm.Controls.Add($objTextBox)
#Label+Textbox second question
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(10,130)
$objLabel2.Size = New-Object System.Drawing.Size(800,20)
$objLabel2.Text = "Serialnumber of the certificate"
$objForm.Controls.Add($objLabel2)
$objTextBox2 = New-Object System.Windows.Forms.TextBox
$objTextBox2.Location = New-Object System.Drawing.Size(10,150)
$objTextBox2.Size = New-Object System.Drawing.Size(200,20)
$objForm.Controls.Add($objTextBox2)
#OK Button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(100,175)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Name = "OK"
#$OKButton.DialogResult = "OK"
$OKButton.Add_Click({
# you never seem to use variable $INT1, but if you do, refer to it as $script:INT1
# set the variables defined OUTSIDE the scriptblock using script-scoping
$script:INT1 = $objTextBox.Text
$script:Serial1 = $objTextBox2.Text
# create a full path and filename for the output of the Invoke-WebRequest call
$crlFile = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'crl.crl'
Invoke-WebRequest -Uri 'http://CENSORED.crl' -OutFile $crlFile
$test = certutil.exe -dump $crlFile | Out-String -Stream | Select-String -Pattern $script:Serial1 -Context 0,1
[void] [Windows.Forms.MessageBox]::Show($test)
# delete the temporary file
$crlFile | Remove-Item -Force
})
$objForm.Controls.Add($OKButton)
[void] $objForm.ShowDialog()
# clean up the form from memory'
$objForm.Dispose()
# here you can again see what is inside variables $INT1 and $Serial1
Write-Host "Last entered values: $INT1 $Serial1"
All user interaction with your form happens in this, blocking call:
[void] $objForm.ShowDialog()
See the ShowDialog() method.
Therefore, if you want to store results from that user interaction in variables, place the assignments after the call, and reference the controls of interest.
# ... form setup
# Show the form modally (as a dialog).
# The call blocks until the form is closed.
# Note:
# You may want to examine the return value in case there
# are multiple ways to close your form, and one of those ways
# signals having *canceled*, for instance.
$null = $objForm.ShowDialog()
# Now you can get values from the controls and assign them to (script) variables.
$INT1 = $objTextBox.Text
$Serial1 = $objTextBox2.Text
Note: If you need to set script-level variables - i.e., variables visible after a .ShowDialog() call - from inside script blocks serving as event delegates (e.g., the script block passed to $OKButton.Add_Click()), you need to use the $script: scope, given that such script blocks run in a child scope too - see this answer.
I would like to have a tech enter in the username and the group name but in one input box. Anyone willing to tell me how to do this?
Function add-togroup{
#Adds members to group in AD
#$users = Read-Host "Enter a username"
Add-Type -AssemblyName Microsoft.VisualBasic;
$value = [Microsoft.VisualBasic.Interaction]::InputBox('Enter username',
'Username')
$value2 = [Microsoft.VisualBasic.Interaction]::InputBox('Enter group
name', 'XA Group','')
$group_membership = Get-ADPrincipalGroupMembership $users | select name |
format-table -auto
foreach($u in $value)
{
Add-ADGroupMember $value2 -Members $u
}
Write-Host $group_membership
}
So I am capable of using multiple dialogs in sequence but it would make for a better user experience if I could roll this into one single box /form.
If you are not satisfied with the basic forms available then one option you have is to roll your own in PowerShell with .Net forms. Just to show an example that you can build from...
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Data Entry Form"
$objForm.Size = New-Object System.Drawing.Size(300,200)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({
if ($_.KeyCode -eq "Enter" -or $_.KeyCode -eq "Escape"){
$objForm.Close()
}
})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please enter the information in the space below:"
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,40)
$objTextBox.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($objTextBox)
$objTextBox2 = New-Object System.Windows.Forms.TextBox
$objTextBox2.Location = New-Object System.Drawing.Size(10,70)
$objTextBox2.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($objTextBox2)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void]$objForm.ShowDialog()
$objTextBox.Text
$objTextBox2.Text
The borrows heavily from the great primer on the subject on TechNet which you should read as it walks you though this better. I removed some of the variable population logic as it was flawed and added another text box. The last two lines return the values entered by the "user". Aside from the addition of the text box I have left most other cosmetic changes up to you to help you get a better understanding of what is involved here.
Keep in mind the locations and sizes of newly added objects and be sure you actually add it to the form.
Since there is not GUI for form building it can seem daunting but its not really that hard to do. You just need to experiment. If you are so inclined there are 3rd party tools that will help with that.
I am writing a Powershell script to automate the install of printers on our network. I have overthinking in place however, I cant seem to get my command button to allow the user to select the printer from a list and set it as default.
I have a string setup to define the printers (4 of them) but no matter what way I code the $OKButton.Add_Click it wont go with the users selection.
Here is the code I have. Can someone please tell me what I am missung?
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Select a Printer"
$objForm.Size = New-Object System.Drawing.Size(400,200)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$x=$objListBox.SelectedItem;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$objForm.Close()}})
#Ok Button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$x=$objListBox.SelectedItem;$strPrinter,$objForm.Close()})
$objForm.Controls.Add($OKButton)
#Cancel Button
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please select a printer:"
$objForm.Controls.Add($objLabel)
#List box showing printer options
$objListBox = New-Object System.Windows.Forms.ListBox
$objListBox.Location = New-Object System.Drawing.Size(10,40)
$objListBox.Size = New-Object System.Drawing.Size(360,20)
$objListBox.Height = 80
[void] $objListBox.Items.Add("HP Color LaserJet CP2020")
[void] $objListBox.Items.Add("Brother DCP-8065DN")
[void] $objListBox.Items.Add("Canon iR-ADV C2220/2230")
[void] $objListBox.Items.Add("HP LJ300-400 color M351-M451")
$objForm.Controls.Add($objListBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
#String to call printers, each printer is assigned a value (1,2,3,4)
$strPrinter = 1, "HP Color LaserJet CP2020", ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\\PS\PT01'))
$strPrinter = 2, "Brother DCP-8065DN", ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\\PS\PT02'))
$strPrinter = 3, "Canon iR-ADV C2220/2230", ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\\PS\PT03'))
$strPrinter = 4, "HP LJ300-400 color M351-M451", ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\PS\PT04'))
$x
There are two problems with your current script.
The first one, that $x appears empty is due to a scoping issue. When inside the scope of the add_Click() event handler, $x is a local variable and its value won't be accessible outside of the event handler.
You could work around this by specifying a parent scope, like (notice the global: scope qualifier):
$global:x = $objListBox.SelectedItem
But still, nothing would happen, leading me to the second issue:
I'm not sure what you mean by "string setup", but your script basically ends up setting the last printer as the default whenever it runs.
You'll want to define the printers up front, before showing the dialog, and wrap the ((New-Object... statements in a scriptblock, something like:
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
# New array of "printer objects", rather than $strPrinter
$Printers = #(
New-Object psobject -Property #{
Name = "HP Color LaserJet CP2020"
SetCommand = { ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\\PS\PT01')) }
},
New-Object psobject -Property #{
Name = "Brother DCP-8065DN"
SetCommand = { ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\\PS\PT02')) }
},
New-Object psobject -Property #{
Name = "Canon iR-ADV C2220/2230"
SetCommand = { ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\\PS\PT03')) }
},
New-Object psobject -Property #{
Name = "HP LJ300-400 color M351-M451"
SetCommand = { ((New-Object -ComObject WScript.Network).SetDefaultPrinter('\PS\PT04')) }
}
)
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Select a Printer"
$objForm.Size = New-Object System.Drawing.Size(400,200)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$x=$objListBox.SelectedItem;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$objForm.Close()}})
#Ok Button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({
# Grab the printer array index
$index = $objListBox.SelectedIndex
# Execute the appropriate command
& $Printers[$index].SetCommand
# Exit
$objForm.Close()
})
$objForm.Controls.Add($OKButton)
#Cancel Button
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please select a printer:"
$objForm.Controls.Add($objLabel)
#List box showing printer options
$objListBox = New-Object System.Windows.Forms.ListBox
$objListBox.Location = New-Object System.Drawing.Size(10,40)
$objListBox.Size = New-Object System.Drawing.Size(360,20)
$objListBox.Height = 80
foreach($Printer in $Printers){
[void] $objListBox.Items.Add($Printer.Name)
}
$objForm.Controls.Add($objListBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
Since the printers are now added to the listbox in correct order, we can simply use the SelectedIndex property to find the original printer object and invoke the scriptblock that sets it as default
I have powershell code where user enters text into an input box, but when I attempt to write this output to screen, it is blank
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Data Entry Form"
$objForm.Size = New-Object System.Drawing.Size(300,200)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$x=$objTextBox.Text;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$objForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$x=$objTextBox.Text;$objForm.Close()})
$objForm.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please enter the information in the space below:"
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,40)
$objTextBox.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($objTextBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
write-host "x is $x"
The output to console is
x is
I thought that
$OKButton.Add_Click({$x=$objTextBox.Text;$objForm.Close()})
Would read the input into $x if the "OK" button was clicked.
You're running into a problem with the Scope of your variable $x. Scope inheritance flows downhill only by default, not uphill. The script block in your $OKButton.Add_Click line is a child scope of the script and any variable changes inside that scope are not written up to the parent scope.
Here's a better description than I could write.
I've run into this in the past and one possible solution is to declare your $x variable earlier in your script so that it is created in the "Script" scope and then specifically reference that script scope variable in your $OKButton.Add_Click line with $script:x=$objTextBox.Text
Note the differences of output in these two simple one-liners that display the difference in the scope. $Local:A is the child $A variable inside the local scope of the script block and $script:A is the parent $A variable in the entire script scope:
$A = "Yes" ; $A ; &{$local:A = "No" ; $A} ; $A
Yes
No
Yes
$A = "Yes" ; $A ; &{$script:A = "No" ; $A} ; $A
Yes
No
No
EDIT: I had a chance to test this and simply stating that the $x variable should be in the script scope is fine, you don't have to declare it earlier as I mentioned before. This should work fine:
$OKButton.Add_Click({$script:x=$objTextBox.Text;$objForm.Close()})
You can solve this by replacing
{$x=$objTextBox.Text;$objForm.Close()}})
with
{Write-Host "x is" $objTextBox.Text;$objForm.Close()}})
In the scenario you present, it should do the same thing.
I'm looking to find out how to automatically set the keyboard focus to a text box in powershell.
I have a script that asks the user to select an option from a dropdown menu, then based on that selection, they have to input certain parameters. That all works fine.
For ease of use, I'd like the focus of the keyboard to shift to the input box each time a new one is shown, so the user doesn't have to keep clicking on it to enter some text.
My code so far :
function inputBox($parameter)
{
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = $parameter
$objForm.Size = New-Object System.Drawing.Size(300,200)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$paramValue=$objTextBox.Text;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$objForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,90)
$OKButton.Size = New-Object System.Drawing.Size(75,25)
$OKButton.Text = "OK"
$OKButton.Add_Click({$paramValue=$objTextBox.Text;$objForm.Close()})
$objForm.Controls.Add($OKButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = $parameter
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,40)
$objTextBox.Size = New-Object System.Drawing.Size(260,20)
$objForm.KeyPreview = $True
$objForm.Controls.Add($objTextBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
return $paramValue
Any ideas?
Thanks!
Following your code, where I can find a dropdown menu, to give focus to textbox when form is shown I've done:
$objForm.Add_Shown({$objForm.Activate(); $objTextBox.focus()})
Try the Select method in case Focus() doesn't work:
if($textbox.CanFocus)
{
$textbox.Focus()
}
else
{
$textbox.Select()
}
Works great, but I needed to make the paramValue variable script global (the scope inside the Add_Click routine is not the script scope and the paramValue setting is lost). Using $Global:paramValue worked for me.
This is an alternative to the answers of #CB and #Shay Levy (none of them worked for me):
$objForm.Add_Shown( { $objTextBox.Select() })
I'd like to add two additional alternative solutions to this problem.
Just move your $objTextBox creation code in first position above $OKButton.
The Code always initializes and prioritizes top to bottom. With this little change it will select your TextBox first.
And for a second solution add a TabIndex to prioritize manually.
$objTextBox.TabIndex = 0
$OKButton.TabIndex = 1
Lower numbers are prioritized and are focused first.
The code that works with the original post and FOCUS is:
if ( $objTextBox.CanFocus )
{
$objTextBox.Focus()
}
else
{
$objTextBox.Select()
}