# Your Script Can Have a Window Too

On Windows, a script can open a small window of its own with .NET's Windows Forms: a form, a list, a button and a label, with a script block that runs when the button is clicked, so a colleague who would rather click can use your tool without typing a command.

Freelancer · PowerShell · Updated November 10, 2026

## After this lesson you can

- Build a small Windows Forms window with a list and a button from PowerShell, and show it with ShowDialog()
- Run code when a button is clicked with Add_Click, reading the item chosen in the list
- Choose between objects, a saved file, colors on the screen and a window for what a tool gives back

## Key takeaways

- `Add-Type -AssemblyName System.Windows.Forms` loads Windows Forms; `[System.Windows.Forms.Form]::new()` makes a window you set up through its properties.
- A ListBox shows a list, a Button is something to click, and a Label shows text; `$form.Controls.AddRange()` puts them in the window.
- `$button.Add_Click({ … })` gives the button a script block to run on every click, and it can read `$list.SelectedItem`.
- `$form.ShowDialog()` shows the window and waits until it's closed. Windows Forms is Windows only.
- A tool hands back objects for commands, a file for next time, colors for the person watching, and a window for someone who would rather click.

Your recipe box works, and your roommate wants to use it. They don't want to open a terminal, though, and they shouldn't have to. They want what every app gives them: a window, a list to pick from, and a button. *The Terminal and the Mouse: Two Ways In* promised options, not sides, and here's one of the best: the same script can open a small window of its own. You've had a taste already with `Out-GridView`, from *Reports: `ConvertTo-Html` and `Out-GridView`*; this lesson builds a window that's yours.

**Windows only.** The window comes from **Windows Forms**, part of .NET on Windows. On a Mac or Linux, `Add-Type` can't find it and stops with an error. It works the same in Windows PowerShell 5.1.

## A window is an object too

Windows Forms is the set of window parts that .NET, the toolbox PowerShell is built on, has had for over twenty years. `Add-Type` loads it, and `[System.Windows.Forms.Form]::new()` makes a window, a **form**. The square brackets name a type, as `[datetime]` did in *Numbers, Dates and Types*, and `::new()` (read "new") makes one. Like everything in PowerShell, the window is an object with properties. Make a file `Show-RecipeWindow.ps1` in your recipe-box folder. Each block that builds the window goes into it, in order; paste them at the prompt as well, and the short blocks that only look at the window are for the prompt. Start like this:

```powershell
Add-Type -AssemblyName System.Windows.Forms
$form = [System.Windows.Forms.Form]::new()
$form.Text = 'Recipe Box'
$form.ClientSize = [System.Drawing.Size]::new(320, 250)
$form.StartPosition = 'CenterScreen'
```

`Text` is the title bar, `ClientSize` the inside's width and height in pixels, and `CenterScreen` puts it in the middle of the screen. At the prompt, look at the window you made:

```powershell
$form | Select-Object -Property Text, ClientSize, StartPosition, Visible
```

```text
Text       ClientSize              StartPosition Visible
----       ----------              ------------- -------
Recipe Box {Width=320, Height=250}  CenterScreen   False
```

`Visible` is `False`: the window exists, but nothing has shown it yet.

## A list, a button and a label

The parts of a window are **controls**. A ListBox shows a list to pick from; add the recipes from `RecipeBox.ps1`, from *Where a Tool Keeps Its Data*:

```powershell
. "$HOME\terminal-practice\recipe-box\RecipeBox.ps1"
$list = [System.Windows.Forms.ListBox]::new()
$list.Location = [System.Drawing.Point]::new(10, 10)
$list.Size = [System.Drawing.Size]::new(300, 160)
foreach ($recipe in Get-Recipe) { [void]$list.Items.Add($recipe.Name) }
```

`Location` is counted in pixels from the window's top-left corner. `Items.Add` hands back the new item's position, a number that would print on the screen; `[void]` in front (read "throw away") drops it. A Button is something to click, and a Label shows a line of text:

```powershell
$button = [System.Windows.Forms.Button]::new()
$button.Text = 'How long?'
$button.Location = [System.Drawing.Point]::new(10, 185)
$button.Size = [System.Drawing.Size]::new(100, 30)
$label = [System.Windows.Forms.Label]::new()
$label.Location = [System.Drawing.Point]::new(120, 192)
$label.AutoSize = $true
$form.Controls.AddRange(@($list, $button, $label))
```

The last line puts all three in the window. Check them:

```powershell
$form.Controls | Select-Object -Property Text, Location
```

```text
Text      Location
----      --------
          {X=10,Y=10}
How long? {X=10,Y=185}
          {X=120,Y=192}
```

The list, the button with its text, and the label, empty until there's something to say.

## When the button is clicked: \`Add_Click\`

A click is an **event**, something that happens while the window is open. `Add_Click` (read "add to Click") gives the button a **script block**, code in `{ }` that waits until it's needed, and Windows Forms runs it on every click:

```powershell
$button.Add_Click({
    $recipe = Get-Recipe | Where-Object Name -eq $list.SelectedItem
    if ($recipe) { $label.Text = "$($recipe.Minutes) minutes" }
    else { $label.Text = 'Pick a recipe first.' }
})
```

`$list.SelectedItem` is the name the person picked, or nothing yet. Last, show the window:

```powershell
$form.ShowDialog() | Out-Null
$form.Dispose()
```

`ShowDialog()` (read "show dialog") opens the window and waits: the script stops on that line until the window is closed. It hands back how it was closed, such as `Cancel`, which `Out-Null` drops. `Dispose()` frees the window afterward. Run `.\Show-RecipeWindow.ps1`, pick *Pancakes*, click **How long?**, and the label says *20 minutes*:

![The Recipe Box window: a list of seven recipes with Pancakes highlighted, a How long? button below the list, and the words 20 minutes beside the button](https://www.ask-codey.com/lessons/TODO)

For a colleague, give the script a desktop shortcut, from *Run It from Anywhere*, with `-WindowStyle Hidden` in place of `-NoExit`, so only your window stays on the screen.

**A common mistake.** The click block reads `$list` and `$label` when it runs, not when you write it. Build the window inside a function that hands it back, show it somewhere else, and by the time of the click those variables are gone: nothing happens, and the console says the property `Text` cannot be found. Add `.GetNewClosure()` after the block's closing brace, `$button.Add_Click({ … }.GetNewClosure())`, and it keeps its own copy of them.

## Which way out?

A real tool often gives back more than one thing, and each goes the way that fits its reader:

| Who or what needs it | Give it | Lesson |
|---|---|---|
| The next command: sort, filter, export | Objects, with `[PSCustomObject]` | *Objects of Your Own: `[PSCustomObject]`* |
| The tool itself, next time | A JSON file in `$env:LOCALAPPDATA` | *Where a Tool Keeps Its Data* |
| The person watching the terminal | `Write-Progress`, colors with `Write-Host` | *Friendly Output: Colors and Progress* |
| Someone who would rather click | A Windows Forms window | this lesson |

The terminal isn't always the right door, and now your tools can have both.

## Check yourself

2 questions to try on the lesson page.

## So what

A window is one more object your script can make: a few controls, a click that runs a script block, and `ShowDialog()`, and the tool you built in the terminal has a door for people who would rather click.

## Key terms

- [Windows Forms](https://www.ask-codey.com/glossary/windows-forms)
- [Graphical User Interface](https://www.ask-codey.com/glossary/graphical-user-interface)
- [.NET Types in PowerShell](https://www.ask-codey.com/glossary/dotnet-types-in-powershell)
- [Script Block](https://www.ask-codey.com/glossary/script-block)
- [Add-Type](https://www.ask-codey.com/glossary/add-type)

## Cheat sheets

- [Your Script Can Have a Window Too](https://www.ask-codey.com/cheat-sheets/powershell-windows-forms-cheat-sheet)

## Related

- [The Terminal and the Mouse: Two Ways In](https://www.ask-codey.com/lessons/powershell-terminal-and-gui)
- [Reports: ConvertTo-Html and Out-GridView](https://www.ask-codey.com/lessons/powershell-reports)
- [Run It from Anywhere](https://www.ask-codey.com/lessons/powershell-run-from-anywhere)
- [Friendly Output: Colors and Progress](https://www.ask-codey.com/lessons/powershell-friendly-output)

Source: https://www.ask-codey.com/lessons/powershell-windows-forms
