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()
}
Related
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()
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.
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).
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 would like to prompt user to enter a list of passwords, one line at a time. When the person types the passwords, it should appear as *
I have a function
function Read-MultiLineInputBoxDialogPwd([string]$Message, [string]$WindowTitle, [string]$DefaultText){
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms
# Create the label
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(10,10)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.AutoSize = $true
$label.Text = $Message
# Create the TextBox used to capture the user's text
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Size(10,40)
$textBox.Size = New-Object System.Drawing.Size(575,200)
$textBox.AcceptsReturn = $true
$textBox.AcceptsTab = $false
$textBox.Multiline = $true
$textBox.ScrollBars = 'Both'
$textBox.Text = $DefaultText
$textBox.UseSystemPasswordChar = $True
# Create the OK button.
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Size(415,250)
$okButton.Size = New-Object System.Drawing.Size(75,25)
$okButton.Text = "OK"
$okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })
# Create the Cancel button.
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Size(510,250)
$cancelButton.Size = New-Object System.Drawing.Size(75,25)
$cancelButton.Text = "Cancel"
$cancelButton.Add_Click({ $form.Tag = $null; $form.Close() })
# Create the form.
$form = New-Object System.Windows.Forms.Form
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size(610,320)
$form.FormBorderStyle = 'FixedSingle'
$form.StartPosition = "CenterScreen"
$form.AutoSizeMode = 'GrowAndShrink'
$form.Topmost = $True
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.ShowInTaskbar = $true
# Add all of the controls to the form.
$form.Controls.Add($label)
$form.Controls.Add($textBox)
$form.Controls.Add($okButton)
$form.Controls.Add($cancelButton)
# Initialize and show the form.
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() > $null # Trash the text of the button that was clicked.
# Return the text that the user entered.
return $form.Tag
}
And I call the function
$multiLineTextPwd = Read-MultiLineInputBoxDialogPwd -Message "All possible passwords" -WindowTitle "Passwords" -DefaultText "Please enter all possible passwords, one line at a time..."
But when it pops up, the text still appears in plaintext, even though I set the following
$textBox.UseSystemPasswordChar = $True
How to fix this?
I honestly feel that this would be better accomplished by having a single-line text box and an 'Add Another Password' button where the user could enter a password, and then click the button to add another password. You would just keep adding them to an array, and would have to make sure that when they submit that it checks for anything in that box and adds anything left to the array before performing actions.
All password masking references when I went and looked at the MSDN listing for the Textbox class all specifically state Single Line Textbox, so it may well be that you can't use masking with a multiline textbox.
If you read the documentation here you'll see that for multiline text boxes, the UseSystemPasswordChar has no effect.
implement keydown event of mutiline textbox, append * into TB, and append key code string into a string variable.