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

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, 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:

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
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:

Install-PSResource Pester

Then, from the exercise folder:

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:

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:

Set-Location $HOME
Get-Recipe | Sort-Object Minutes
Show-Recipe
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:

    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:

    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

Related