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.
- Objects, not text.
Get-Recipehands back one[PSCustomObject]per recipe, withName,MinutesandCategory, in that order and withMinutesas a number.Add-Recipehands back the recipe it added. - One place for the data.
Get-RecipeFilebuilds the path withJoin-Pathfrom$env:LOCALAPPDATA,RecipeBoxandrecipes.json(on a Mac or Linux, from[Environment]::GetFolderPath('LocalApplicationData')), andSave-RecipeFilemakes theRecipeBoxfolder on the first run. - Colors on the screen only. Every name
Get-Recipehands back is plain, and a newShow-Recipewrites each recipe withWrite-Host, the ones of 20 minutes or less in green. - A window for people who would rather click (Windows).
New-RecipeWindowfills its list with every recipe's name, and the How long? button shows the chosen recipe's minutes.Show-RecipeBoxopens 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-PathandNew-Item -ItemType Directorymake 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-Recipeand read$list.SelectedItem; leave.GetNewClosure()where it is, so the block keeps$listand$label(Your Script Can Have a Window Too). - The old
recipes.jsonin the exercise folder isn't needed once job 2 is done:Remove-Item .\recipes.jsondeletes 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.
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.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 mainIf 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 mainPaste 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.