# Exercise: A Recipe Box Module

Turn a recipe-box module that prints lines of text into a real tool, so its recipes come back as objects you can sort and filter, live in a folder of their own under $env:LOCALAPPDATA, show in color on the screen without a color in the data, and fill a small window with a list and a button, while a Pester check tries it on a recipe box of its own.

Freelancer · PowerShell · Updated November 10, 2026

This is the chapter's whole idea in one exercise: a script that does a job becomes a tool someone else can live with. `RecipeBox.psm1` is a recipe box that runs, but it isn't a real tool yet. Its recipes come back as lines of text that won't sort, it saves them in whatever folder you happen to be in, it bakes colors into the recipes' names, and its window shows an empty list. Your job is to finish it the way this chapter showed, so a friend could use it from the prompt, and a roommate who'd rather click could use it from a window. A Pester check tries your module on a recipe box of its own and tells you which jobs are done.

## Get the exercise code

Open this lesson's GitHub repository, [github.com/ask-codey/exercise-powershell-a-recipe-box-module](https://github.com/ask-codey/exercise-powershell-a-recipe-box-module), also linked from the **Exercise** box beside the lesson. Click **Code**, then **Download ZIP**, and unzip it into your `terminal-practice` folder. Or clone it with Git.

The folder holds:

- `RecipeBox.psm1`: the recipe box's commands, for you to finish.
- `answers.txt`: three questions, each with a numbered line for your answer.
- `Check.Tests.ps1`: the check. You don't need to change it.

## What to do

A `.psm1` file is a **module**: a script file of commands that `Import-Module` loads into your window, where they work like any other command until you close it. Open PowerShell in the exercise folder, the one with `answers.txt` in it, and open it in VS Code with `code .`. Load the module and try it as it is:

```powershell
Import-Module .\RecipeBox.psm1 -Force
Add-Recipe -Name 'Lemon Garlic Pasta' -Minutes 30 -Category Dinner
Add-Recipe -Name 'Overnight Oats' -Minutes 10 -Category Breakfast
Get-Recipe | Sort-Object Minutes
```

```text
Added Lemon Garlic Pasta.
Added Overnight Oats.
Lemon Garlic Pasta - 30 min - Dinner
Overnight Oats - 10 min - Breakfast
```

The sort changed nothing, the oats show up green in a list that should be plain, and a `recipes.json` has appeared in the exercise folder. After every change you save, run `Import-Module .\RecipeBox.psm1 -Force` again: `-Force` loads the new version.

1. **Objects, not text.** `Get-Recipe` hands back one `[PSCustomObject]` per recipe, with `Name`, `Minutes` and `Category`, in that order and with `Minutes` as a number. `Add-Recipe` hands back the recipe it added.
2. **One place for the data.** `Get-RecipeFile` builds the path with `Join-Path` from `$env:LOCALAPPDATA`, `RecipeBox` and `recipes.json` (on a Mac or Linux, from `[Environment]::GetFolderPath('LocalApplicationData')`), and `Save-RecipeFile` makes the `RecipeBox` folder on the first run.
3. **Colors on the screen only.** Every name `Get-Recipe` hands back is plain, and a new `Show-Recipe` writes each recipe with `Write-Host`, the ones of 20 minutes or less in green.
4. **A window for people who would rather click** (Windows). `New-RecipeWindow` fills its list with every recipe's name, and the **How long?** button shows the chosen recipe's minutes. `Show-RecipeBox` opens it.

The README in the folder says each job in more detail. Then answer the three questions in `answers.txt`, using *Friendly Output: Colors and Progress* and *Your Script Can Have a Window Too*.

## Check your work

The check uses Pester. If you haven't installed it yet, install it once:

```powershell
Install-PSResource Pester
```

Then, from the exercise folder:

```powershell
Invoke-Pester
```

At first most tests fail. That's expected: each job you finish turns its tests green. A failing test names the job and says what to look at, for example *Expected 'Black Bean Tacos', because Add-Recipe should hand back the recipe object it saved, not a sentence about it*. Each test points `$env:LOCALAPPDATA` at a throwaway folder, so it starts with an empty box and never touches your own recipes. On a Mac or Linux, the two window tests are skipped.

If PowerShell says the check can't be loaded because it is not digitally signed, Windows has marked the downloaded files as coming from the internet: from the exercise folder, run `Get-ChildItem -Recurse | Unblock-File` once, then `Invoke-Pester` again.

## Hints

- **Text has one property, its `Length`.** That's why the starter's sort changes nothing (*Objects of Your Own: `[PSCustomObject]`*).
- **The first run.** `Split-Path`, `Test-Path` and `New-Item -ItemType Directory` make the folder only when it's missing (*Where a Tool Keeps Its Data*).
- **A color in a name is part of the name.** `Where-Object Name -eq 'Overnight Oats'` can't find a name with a color code in front of it (*Friendly Output: Colors and Progress*).
- **The click block runs later.** It can call `Get-Recipe` and read `$list.SelectedItem`; leave `.GetNewClosure()` where it is, so the block keeps `$list` and `$label` (*Your Script Can Have a Window Too*).
- **The old `recipes.json`** in the exercise folder isn't needed once job 2 is done: `Remove-Item .\recipes.json` deletes that one file.

## When you're done

Every test is green, and Pester's last line says:

```text
Tests Passed: 16, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
```

Load the module again and add the two recipes once more (the starter kept the first ones in the exercise folder's `recipes.json`). Then go to any other folder, and they're still there, as objects and then on the screen:

```powershell
Set-Location $HOME
Get-Recipe | Sort-Object Minutes
Show-Recipe
```

```text
Name               Minutes Category
----               ------- --------
Overnight Oats          10 Breakfast
Lemon Garlic Pasta      30 Dinner

Lemon Garlic Pasta, 30 min, Dinner
Overnight Oats, 10 min, Breakfast
```

On your screen, the oats are green. Then run `Show-RecipeBox`, pick *Overnight Oats*, and click **How long?**: the window says *10 minutes*. Your recipes now live in `RecipeBox\recipes.json` under `$env:LOCALAPPDATA`; to empty the box, look first with `Remove-Item (Split-Path (Get-RecipeFile)) -Recurse -WhatIf`, and the same line without `-WhatIf` deletes it.

## Submit your exercise

The exercise is how you finish this lesson. When your work is done, put it on GitHub in a repo of your own, then submit that repo's address here.

1. On GitHub, click **+**, then **New repository**. Name it (for example `exercise-powershell-a-recipe-box-module`), choose **Public**, and click **Create repository**. Leave out the README: your folder already has one.
2. In a terminal in your exercise folder (PowerShell works), send your work to your new repo. If you downloaded the ZIP:

   ```powershell
   git init -b main
   git add .
   git commit -m "My solution"
   git remote add origin https://github.com/your-name/exercise-powershell-a-recipe-box-module.git
   git push -u origin main
   ```

   If you cloned the starter instead, point your copy at your own repo, then commit and push:

   ```powershell
   git remote set-url origin https://github.com/your-name/exercise-powershell-a-recipe-box-module.git
   git add .
   git commit -m "My solution"
   git push -u origin main
   ```

3. Paste your repo's address, such as `https://github.com/your-name/exercise-powershell-a-recipe-box-module`, into **Submit your exercise** on this page. The site checks that the repo is public and holds your commits, and then the lesson is complete.

Changed something after you submitted? Push it, and submit again: your latest submission is the one that counts.

## So what

Objects others can sort, data that stays put, color only where people read, and a window for anyone who'd rather click: that's the difference between a script that works for you and a tool other people can use.

## Key terms

- [PSCustomObject](https://www.ask-codey.com/glossary/pscustomobject)
- [Join-Path](https://www.ask-codey.com/glossary/join-path)
- [ConvertTo-Json](https://www.ask-codey.com/glossary/convertto-json)
- [$PSStyle](https://www.ask-codey.com/glossary/psstyle)
- [Windows Forms](https://www.ask-codey.com/glossary/windows-forms)
- [Module](https://www.ask-codey.com/glossary/module-powershell)

## Related

- [Objects of Your Own: \`\[PSCustomObject\]\`](https://www.ask-codey.com/lessons/powershell-custom-objects)
- [Where a Tool Keeps Its Data](https://www.ask-codey.com/lessons/powershell-app-data)
- [Friendly Output: Colors and Progress](https://www.ask-codey.com/lessons/powershell-friendly-output)
- [Your Script Can Have a Window Too](https://www.ask-codey.com/lessons/powershell-windows-forms)

Source: https://www.ask-codey.com/lessons/exercise-powershell-a-recipe-box-module
