WinForms in PowerShell
Tags: powershell, windows, dotnet
Published: 2020-07-07 11:31:59

PowerShell is an advanced shell with integration of .NET objects. It's more than just a replacement for the older `cmd.exe`. It can work with .NET assemblies, process large datasets and even interact with web services.

Because of the .NET assemblies support, it can work with WinForms (or even WPF), making it possible to create scripts with GUIs.

## Requirements

This has been tested to work with Windows PowerShell verion 5.1. It's likely going to work with older versions as well, but it's not going to work with the new cross-platform PowerShell (there are no WinForms on Linux/macOS). You can check the version with

```php
Get-Host | Select-Object version
```

## Setting up the environment

Before we can start, let's check a few things.

The first one is the script execution policy. It controls which scripts can be run. By default, Windows blocks execution of all scripts ([more on that here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies)). We have to allow it to run local scripts that are not digitally signed. It's possible to do this this either by going through *Windows Settings* > *Updates & Security* > *For developers*, checking the *Change execution policy...* checkbox and clicking *Apply*, or just executing

```php
Set-ExecutionPolicy RemoteSigned
```

from administrator PowerShell.

Another (less important) thing is the code editor. Even though we could just write the entire script directly in PowerShell, it's easier to use a full-featured editor with error checking and syntax highlighting. Windows already comes with PowerShell ISE (**I**ntegrated **S**cripting **E**nvironment), but you can use Visual Studio Code with the PowerShell extension.

## Writing our script

Let's start!

### Importing the assemblies

We have to import both `System.Windows.Forms` and `System.Drawing` assemblies. It's possible to only include the first one but we also need the 2nd to specify control sizes.

```php
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
```

You can test it by creating a blank form:

```php
$form = New-Object System.Windows.Forms.Form
$form.ShowDialog()
```

![A blank form](https://dev-to-uploads.s3.amazonaws.com/i/ob7huzgggh0pj8krtgi2.png)

### Adding controls

Let's make a "Hello World" form. First, we create a top-level `Form` object:

```php
$form = New-Object System.Windows.Forms.Form
$form.Text = "Some form"
$form.Size = New-Object System.Drawing.Size(150, 145)
$form.AutoSize = $true
```

In PowerShell, objects are created using `New-Object`. You can also pass parameters to constructors, similar to the `new` keyword in C#. Values are assigned to properties directly. Another difference is using `$true` instead of just `true`.

Let's add a label and a button:

```php
$lbl1 = New-Object System.Windows.Forms.Label
$lbl1.Text = "Hello World!"
$lbl1.Location = New-Object System.Drawing.Point(30, 20);

$btn = New-Object System.Windows.Forms.Button
$btn.Text = "Close"
$btn.location = New-Object System.Drawing.Point(30, 60);
$btn.DialogResult = [System.Windows.Forms.DialogResult]::OK
```

The `$btn.DialogResult` line tells the form what to return when the button is clicked. You can use this to figure out whether the user clicked OK or Cancel. We also make `$btn` the default button and lay controls onto the form:

```php
$form.AcceptButton = $btn
$form.controls.Add($lbl1)
$form.controls.Add($btn)
```

All that's left is showing the form itself:

```php
$form.ShowDialog()
```

![A blank form](https://dev-to-uploads.s3.amazonaws.com/i/ob7huzgggh0pj8krtgi2.png)

### Event handlers

In our form, `$btn` is the default OK button which just terminates the form. But we can use non-terminating event handlers as well. For example, let's make it possible to click on the label:

```php
$lbl1.Add_Click({
    [System.Windows.Forms.MessageBox]::Show("Hey!")
})
```

You can call functions from event handlers as you normally would.

### Visual Styles

Something I've noticed with these GUI scripts is that different control styles are used when the script is run from the PowerShell console instead of VSCode. The console uses legacy rendering which falls back to using Windows 95-style controls. We need to enable Visual Styles to fix that:

```php
[System.Windows.Forms.Application]::EnableVisualStyles()
```

---

From here on, you can add more controls and event handlers. If you've used C#/VB.NET before you can reuse large parts of that knowledge to create nicer and more powerful scripts.

That's it for now 😉

---

The full script:

```php
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

[System.Windows.Forms.Application]::EnableVisualStyles()

$form = New-Object System.Windows.Forms.Form
$form.Text = "Some form"
$form.Size = New-Object System.Drawing.Size(150, 145)
$form.AutoSize = $true

$lbl1 = New-Object System.Windows.Forms.Label
$lbl1.Text = "Hello World!"
$lbl1.Location = New-Object System.Drawing.Point(30, 20);

$lbl1.Add_Click({
    [System.Windows.Forms.MessageBox]::Show("Hey!")
})

$btn = New-Object System.Windows.Forms.Button
$btn.Text = "Close"
$btn.location = New-Object System.Drawing.Point(30, 60);
$btn.DialogResult = [System.Windows.Forms.DialogResult]::OK

$form.AcceptButton = $btn
$form.controls.Add($lbl1)
$form.controls.Add($btn)

$Null = $form.ShowDialog()
```