
Overview of WinForms
In the remainder of this chapter, we will work through some examples which cover the key aspects of WinForms based GUI development using Windows PowerShell.
The PowerShell scripts generated by PoshProTools can be used in any PowerShell host. The designer and code-behind files can be joined into a single script. You can use the bundling functionality of PoshProTools to do this automatically.
Even though PowerShell is designed primarily to act as a text-based environment, you can attach a GUI interface to your PowerShell scripts using . Interestingly, the one thing that I never hear anyone talk about is how using forms changes the overall logic of PowerShell scripts.
In many ways, these changes simplify the scripting process. At the same time, when you are used to the normal PowerShell way of doing things, it can be a bit tough to wrap your head around why that is.
Настал тот момент когда необходимо уходить от всех зарубежных программ удаленного подключения, во всяком случае у нас в компании. Посмотрев отечественные аналоги мы пришли в ужас от стоимости и качества работы ПО. Поразмыслив какой функционал нам необходим для подключения 1-линии к пользователям поняли:
Список ПК к которым нужно подключиться
Управление ПК без согласия пользователя
А значит нам будет достаточно простого powershell’a.
Для выполнения дальнейших действий необходимо установить модуль Active Directory для PowerShell
Add-Type -assembly System.Windows.Forms
$window_form = New-Object System.Windows.Forms.Form
$window_form.Text ='Подключение к удаленному рабочему столу'
$window_form.Width = 240
$window_form.Height = 550
$window_form.StartPosition = 'CenterScreen'
$FormLabel1 = New-Object System.Windows.Forms.Label
$FormLabel1.Text = "Введите логин:"
$FormLabel1.Location = New-Object System.Drawing.Point(10,10)
$FormLabel1.AutoSize = $true
$window_form.Controls.Add($FormLabel1)
$FormTextBox1 = New-Object System.Windows.Forms.TextBox
$FormTextBox1.Width = 200
$FormTextBox1.Location = New-Object System.Drawing.Point(10,25)
$FormLabel2 = New-Object System.Windows.Forms.Label
$FormLabel2.Text = "Введите пароль:"
$FormLabel2.Location = New-Object System.Drawing.Point(10,50)
$FormLabel2.AutoSize = $true
$FormTextBox2 = New-Object System.Windows.Forms.TextBox
$FormTextBox2.Width = 200
$FormTextBox2.text = $NULL
$FormTextBox2.Location = New-Object System.Drawing.Point(10,65)
$FormLabel3 = New-Object System.Windows.Forms.Label
$FormLabel3.Text = "Выберете компьютер для подключения:"
$FormLabel3.Location = New-Object System.Drawing.Point(10,90)
$FormLabel3.AutoSize = $true
$ListBox = New-Object System.Windows.Forms.ListBox
$ListBox.Width = 200
$ListBox.Height = 300
$ListBox.Location = New-Object System.Drawing.Point(10,105)
$FormButton = New-Object System.Windows.Forms.Button
$FormButton.Location = New-Object System.Drawing.Size(10,400)
$FormButton.Size = New-Object System.Drawing.Size(200,30)
$FormButton.Text = "Подключится"
$window_form.AcceptButton = $FormButton
$FormButton2 = New-Object System.Windows.Forms.Button
$FormButton2.Location = New-Object System.Drawing.Size(10,440)
$FormButton2.Size = New-Object System.Drawing.Size(200,30)
$FormButton2.Text = "Закрыть"
$window_form.CancelButton = $FormButton3
$FormButton2.Add_Click({$window_form.Close();$window_form.Visible=$false})
$FormLabel4 = New-Object System.Windows.Forms.Label
$FormLabel4.Text = ""
$FormLabel4.Location = New-Object System.Drawing.Point(10,480)
$FormLabel4.AutoSize = $true
$window_form.Controls.Add($FormButton)
$window_form.Controls.Add($FormButton2)
$window_form.Controls.Add($ListBox)
$window_form.Controls.Add($FormTextBox1)
$window_form.Controls.Add($FormTextBox2)
$window_form.Controls.Add($FormLabel1)
$window_form.Controls.Add($FormLabel2)
$window_form.Controls.Add($FormLabel3)
$window_form.Controls.Add($FormLabel4)
$window_form.Add_Shown({$FormTextBox1.Select()})
$window_form.Topmost = $true
$window_form.MaximizeBox = $false
$result = $window_form.ShowDialog()# Прописываем ваш домен и имя пользователя
$FormTextBox1.text = "$Env:UserDomain\$Env:UserName"
# Поле пароля делаем скрытым
$FormTextBox2.PasswordChar = '*'
# Заполняем свой OU и домен
$OU = "OU=Computers,DC=domain,DC=local"
# Получаем список ПК и вносим его в поле выбора ПК
$comps = (Get-ADComputer -SearchBase $OU -Filter *).Name | Sort-Object
ForEach ($comp in $comps){ [void] $listBox.Items.Add($comp) }Теперь нужно выполнить действие при нажатии на кнопку $FormButton в котором мы должны будем найти активный сеанс пользователя удаленного ПК и подключиться к этому сеансу
# Действие на нажатие кнопки
$FormButton.Add_Click({
# Получаем имя выбранного ПК из списка
$ps = $listBox.SelectedItem.ToString()
# Передаем логин и пароль в переменную
$pwsecur = ConvertTo-SecureString -String $FormTextBox2.text -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $FormTextBox1.text, $pwsecur
# Находим все сессии удаленного ПК и ищем активную $active_sessions = quser /server:$ps | foreach {($_ -replace "\s\s+",",")} | ConvertFrom-Csv ForEach ($active_session in $active_sessions) { #Не проверял работу на англ версии if (($active_session.СТАТУС -eq 'Активно') -or ($active_session.STATUS -eq 'Active')){ $id = $active_session.ID }
# Подключаемся к активной сессии
Start-Process -FilePath "mstsc" -ArgumentList "/v:$ps /shadow:$id /control /noconsentprompt" -Credential $Cred -Wait -WindowStyle MaximizedСкрипт готов, бежим проверять
Так же можем добавить проверки:
Заполнено поле с паролем
С такими проверками полная версия скрипта будет выглядеть так
Hidden text
[Console]::outputEncoding = [System.Text.Encoding]::GetEncoding('cp866')
$OU = "OU=Computers,DC=domain,DC=local"
Add-Type -assembly System.Windows.Forms
$window_form = New-Object System.Windows.Forms.Form
$window_form.Text ='Подключение к удаленному рабочему столу'
$window_form.Width = 240
$window_form.Height = 550
$window_form.StartPosition = 'CenterScreen'
$FormLabel1 = New-Object System.Windows.Forms.Label
$FormLabel1.Text = "Введите логин:"
$FormLabel1.Location = New-Object System.Drawing.Point(10,10)
$FormLabel1.AutoSize = $true
$window_form.Controls.Add($FormLabel1)
$FormTextBox1 = New-Object System.Windows.Forms.TextBox
$FormTextBox1.Width = 200
$FormTextBox1.text = "$Env:UserDomain\$Env:UserName"
$FormTextBox1.Location = New-Object System.Drawing.Point(10,25)
$FormLabel2 = New-Object System.Windows.Forms.Label
$FormLabel2.Text = "Введите пароль:"
$FormLabel2.Location = New-Object System.Drawing.Point(10,50)
$FormLabel2.AutoSize = $true
$FormTextBox2 = New-Object System.Windows.Forms.TextBox
$FormTextBox2.Width = 200
$FormTextBox2.text = $NULL
$FormTextBox2.PasswordChar = '*'
$FormTextBox2.Location = New-Object System.Drawing.Point(10,65)
$FormLabel3 = New-Object System.Windows.Forms.Label
$FormLabel3.Text = "Выберете компьютер для подключения:"
$FormLabel3.Location = New-Object System.Drawing.Point(10,90)
$FormLabel3.AutoSize = $true
$ListBox = New-Object System.Windows.Forms.ListBox
$ListBox.Width = 200
$ListBox.Height = 300
$ListBox.Location = New-Object System.Drawing.Point(10,105)
$comps = (Get-ADComputer -SearchBase $OU -Filter *).Name | Sort-Object
ForEach ($comp in $comps){ [void] $listBox.Items.Add($comp) }
$FormButton = New-Object System.Windows.Forms.Button
$FormButton.Location = New-Object System.Drawing.Size(10,400)
$FormButton.Size = New-Object System.Drawing.Size(200,30)
$FormButton.Text = "Подключится"
$window_form.AcceptButton = $FormButton
$FormButton.Add_Click({ $ps = $listBox.SelectedItem.ToString() If (($FormTextBox2.text).Length -gt 1){ If ($ps -ne $NULL){ Function Test-ADAuthentication { param($username,$password) (new-object directoryservices.directoryentry "",$username,$password).psbase.name -ne $null } $testcred = Test-ADAuthentication $FormTextBox1.text $FormTextBox2.text If ($testcred -eq 'True'){ If (Test-Connection $ps -Count 1 -Quiet){ $FormLabel4.Text = "Подключаемся..." $pwsecur = ConvertTo-SecureString -String $FormTextBox2.text -AsPlainText -Force $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $FormTextBox1.text, $pwsecur Invoke-Command –ComputerName $ps -Credential $cred –ScriptBlock {New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services' -Name 'Shadow' -Value '2' -PropertyType 'DWord'} Invoke-Command –ComputerName $ps -Credential $cred –ScriptBlock {New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services' -Name 'AllowRemoteRPC' -Value '1' -PropertyType 'DWord'} $active_sessions = quser /server:$ps | foreach {($_ -replace "\s\s+",",")} | ConvertFrom-Csv ForEach ($active_session in $active_sessions) { #вот тут не знаю как будет на анг языке if (($active_session.СТАТУС -eq 'Активно') -or ($active_session.STATUS -eq 'Active')){ $id = $active_session.ID } } Start-Process -FilePath "mstsc" -ArgumentList "/v:$ps /shadow:$id /control /noconsentprompt" -Credential $Cred -Wait -WindowStyle Maximized
}else { $FormLabel4.Text = "КОМПЬЮТЕР НЕ ДОСТУПЕН!"
} }else { $FormLabel4.Text = "ЛОГИН ИЛИ ПАРОЛЬ НЕ ВЕРНЫЕ!"
} }else { $FormLabel4.Text = "ВЫ НЕ ВЫБРАЛИ КОМПЬЮТЕР!"
} }else { $FormLabel4.Text = "ВЫ НЕ ВВЕЛИ ПАРОЛЬ!"
} })
$FormButton2 = New-Object System.Windows.Forms.Button
$FormButton2.Location = New-Object System.Drawing.Size(10,440)
$FormButton2.Size = New-Object System.Drawing.Size(200,30)
$FormButton2.Text = "Закрыть"
$window_form.CancelButton = $FormButton3
$FormButton2.Add_Click({$window_form.Close();$window_form.Visible=$false})
$FormLabel4 = New-Object System.Windows.Forms.Label
$FormLabel4.Text = ""
$FormLabel4.Location = New-Object System.Drawing.Point(10,480)
$FormLabel4.AutoSize = $true
$window_form.Controls.Add($FormButton)
$window_form.Controls.Add($FormButton2)
$window_form.Controls.Add($ListBox)
$window_form.Controls.Add($FormTextBox1)
$window_form.Controls.Add($FormTextBox2)
$window_form.Controls.Add($FormLabel1)
$window_form.Controls.Add($FormLabel2)
$window_form.Controls.Add($FormLabel3)
$window_form.Controls.Add($FormLabel4)
$window_form.Add_Shown({$FormTextBox1.Select()})
$window_form.Topmost = $true
$window_form.MaximizeBox = $false
$result = $window_form.ShowDialog()Powershell это очень хорошо, но запускать его жутко не удобно. Есть прекрасная возможность конвертировать полученный скрипт в exe файл
A PowerShell Calculator App
To illustrate this point, I have created a PowerShell calculator that closely resembles the calculator app that comes with Windows. You can see what the app looks like in Figure 1.

GUI Logic 1
This is my PowerShell calculator.
The use of forms changes the way you think about PowerShell script logic. Before explaining why that is, however, it’s important to offer a high-level overview of how part of the script works.
As such, you would need something like this:
It initializes three variables.

GUI Logic 2
This is what the script shown above does.
The point behind all of this is that if you built a calculator app using conventional PowerShell scripting techniques, you would need a loop like the one demonstrated. However, as weird as it may sound, a forms-based script does not need such a loop.
Using DataGridView Control
Below is an example of DataGridView to display the list of services on a Windows computer.
➡️ Navigate to the list of controls.
Windows PowerShell and WinForms Events
Where is the name of the event to be handled and is the code to be executed when the event is triggered. For example, to close the dialog in the above example when the button is clicked:
$button.add_click( $form.close() )
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
function MyFunction
Using ColorDialog
You can allow for color selection in your PowerShell GUI with the System.Windows.Forms.ColorDialog. Below is a simple example to change the form background with ColorDialog.
Click to reveal the code (ColorDialog)
➡️ Navigate to the list of controls.
Using a checkbox
The checkbox is a very common element in forms. The most common usage I have seen for checkboxes is on a survey, or when you agree to a license agreement during a software install. The script below is a nice example of how to enable and disable the OK button by checking the checkbox.
Click to reveal the code (CheckBox)
➡️ Navigate to the list of controls.
Creating a Project
You can create a module or script project.
Select the Module or Script project type, name it and then click Ok.
Basic Windows PowerShell GUI
Note
Note: Before we start creating a form is it important to know that the PowerShell script is run sequentially. So you define your form and show it. But any code after you displayed the form won’t be executed until you close the form.
×
Dismiss this alert.
Adding controls to the form
So, let’s create some controls on our form. Add the code below to your script. Make sure that ShowDialog is at the end of your script.
Loading the Winforms .NET Assembly
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
Once loaded, we are ready to create a simple GUI from within Windows PowerShell.
Working with the Form Designer
The form designer works the same way with any language. You can select items from the Toolbox window and drag them onto your form. Properties of the controls can be set using the Properties window.
Adding controls automatically updates the Form.Designer.ps1 file. Do not edit this file by hand as the editor will simply recreate it after changes are made to the form.
Adding Event Handlers
To do anything interesting, you’ll need to add event handles. You can access a control’s events by selecting it in the designer and clicking the Event button in the Properties window.
Enter the name of your event handler function and click enter.
After you press enter, you will be moved into the code-behind view where you can wire up your event handler.
The event handler will automatically be wired up to your control.
Using a dropdown list (Combobox)
The next control you can add to the form is ComboBox (Dropdown list). The code below is pretty clear I think, we create the combo box and add the items with a single line foreach loop to the list. Again, we also define the position and I have set the size for the control.
Click to reveal the code (ComboBox)
You can set a default value for your dropdown list by selecting the index. Below is output if we’re not configured the default value for the combobox.
➡️ Navigate to the list of controls.
Bringing it all Together
Now that we have seen a simple GUI created in Windows PowerShell and covered some of the basic techniques we can create a slightly more complex GUI.
Note
Tip: The creation of Drawing.Point objects to configure the size and location of controls on the form.
×
Dismiss this alert.
Click to reveal the code
# Load the Winforms assembly
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
# Create the form
$form = New-Object Windows.Forms.Form
#Set the dialog title
$form.text = "PowerShell WinForms Example"
# Create the label control and set text, size and location
$label = New-Object Windows.Forms.Label
$label.Location = New-Object Drawing.Point 50,30
$label.Size = New-Object Drawing.Point 200,15
$label.text = "Enter your name and click the button"
# Create TextBox and set text, size and location
$textfield = New-Object Windows.Forms.TextBox
$textfield.Location = New-Object Drawing.Point 50,60
$textfield.Size = New-Object Drawing.Point 200,30
# Create Button and set text and location
$button = New-Object Windows.Forms.Button
$button.text = "Greeting"
$button.Location = New-Object Drawing.Point 100,90
# Set up event handler to extarct text from TextBox and display it on the Label.
$button.add_click(
$label.Text = "Hello " + $textfield.text
)
# Add the controls to the Form
$form.controls.add($button)
$form.controls.add($label)
$form.controls.add($textfield)
# Display the dialog
$form.ShowDialog()
When adding controls to the form to display them in the GUI. You can add them one by one, or you can add all of them using AddRange property.
Using a Groupbox
GroupBox combines a header with a box around other control. Nest a Grid within GroupBox. With a GroupBox, we can place them within a box that has a header. This makes interfaces easier to understand and interact with.
Click to reveal the code (GroupBox)
➡️ Navigate to the list of controls.
Using a label
➡️ Navigate to the list of controls.
Using a listbox
ListBox stores several text items. It can interact with other controls. In the below example code, we create a list of DNS server, this will be used as a data source. Select a DNS server from the list then click Submit button to check online status of it.
Click to reveal the code (ListBox)
➡️ Navigate to the list of controls.
Using a textbox
Click to reveal the code (TextBox)
Using a PictureBox
PictureBox is a rectangular region for an image. It supports many image formats. It has an adjustable size. It can access image files from your disk or from the Internet.
The image cannot be shown if the script runs on another computer. You can upload the image to any cloud location, get the direct link of the image then Load it into the form.
➡️ Navigate to the list of controls.
Using a button
The buttons we are going to add are Cancel and Enter.
- Cancel button will just close the form and does nothing else.
- Enter button will run our logic to show a text. Add the buttons with the code below, again make sure you add the variables of the buttons to the $form.controls.AddRange
Click to reveal the code (Button)
➡️ Navigate to the list of controls.
Debugging A Form
Once you are ready to test out your form, you can click Start or press F5 from either the designer window or the code-behind window. PoshTools will fire off the script and you can set breakpoints and debug like any other PowerShell script.
And just like that you have a working Windows Form.
Full Source Code of PowerShell Calculator App
So, with all that said, here is the full source code for my PowerShell calculator app:
Parallel Processing in Forms-Based Scripts
This is where the previously mentioned concept of parallel processing comes into play. When you create a script that uses forms, you begin by creating the form itself. You then create any required GUI-based controls and then add them to the form. For example, in the case of my calculator app, each button is a separate object that has been added to the form. The calculator’s display is also an object that has been added to the form.
These GUI objects function independently of the PowerShell script. My calculator app does not contain a loop that monitors for button presses. Instead, when you create a button object and add it to the form, PowerShell will continuously check if that button has been clicked. This behavior happens automatically without requiring you to do anything.
Windows Forms
Building a GUI with Windows Forms in Visual Studio
Using a ProgressBar
Click to reveal the code (ProgressBar)
➡️ Navigate to the list of controls.
Create the Form
After installing the Pro tools, you should now have a Form item template available. Right click on your project and select Add->New Item.
Once the New Item dialog pops up, select the PowerShell Form template, name it and click Ok.
Associating Actions With GUI Objects
All of this is to say that the calculator app records key presses, updates the display, and performs math operations without requiring a central loop. Each button object contains a built-in looping mechanism (which is invisible to PowerShell) that continually checks for keypresses, eliminating the need to build the loop yourself.
Accessing Controls Added to the Form
In many circumstances, you’ll want to access a control that you’ve add to the form. The $MainForm variable will have parameters for each of it’s child items. You can access those through the control’s name.
$MainForm.lblMyLabel.Value = "Some Text"Using a RadioButton
- Yes — I like coffee.
- No — I don’t like coffee.
- Sometimes — depending on the type of coffee.
To set this up in PowerShell we have to do a few things:
- Create a group for the radio buttons,
- Create the radio buttons and give them values,
- Create the form and add the usual Submit and Cancel buttons.
The code for all of this is below:
Click to reveal the code (RadioButton)
➡️ Navigate to the list of controls.
Linear Execution vs. Parallel Execution in PowerShell Scripts
As I’m sure you know, a basic PowerShell script is linear in its execution. This means that each line of code executes sequentially, one after the other, starting from the first and progressing to the last line of the script.
Of course, not all PowerShell scripts are linear. In more advanced PowerShell scripts, you may encounter functions and other logic branching mechanisms. Even in these cases, the script still executes serially, where each instruction is carried out one at a time.
Graphical PowerShell scripts that have Windows forms use serial execution just like any other PowerShell script. However, certain elements running behind the scenes are using parallel execution. The distinction is important because when you are creating a graphical PowerShell script, you will typically refrain from creating a main script loop.
Setting WinForms Properties
WinForms components are essentially objects, and as such, have properties which can be set to alter component appearance and behavior. As with other object properties, these can be accessed using standard dot notation. In the previous example, we used this approach to set the text of the button control:
Similarly, we could set the title of the form using the form object’s text property:
$form.text = "PowerShell WinForms Example"
About the Author(s)

Brien Posey is a bestselling technology author, a speaker, and a 20X Microsoft MVP. In addition to his ongoing work in IT, Posey has spent the last several years training as a commercial astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space.



