Exercise: The Bike Shop's Monthly Report
Turn Maple Street Bikes' messy October exports into the owner's monthly report, cleaning the sales and rentals CSV files, adding them up per product and per store, changing one setting in a JSON file and making a web page of the totals, while a Pester check tells you which steps are right.
Journeyman · PowerShell · Operating System · Updated
It's the first of November at Maple Street Bikes, which now has a third shop, Hillcrest. The till and the rental desk have exported October, as messy as ever, and the owner wants the month's report by lunchtime. Bookkeepers and office managers spend a morning on this every month, in Excel. You'll do it with the commands from this chapter, five steps, each saved as a file, and a check tells you which steps are right. Next month, the same commands do it again in a minute.
Get the exercise code
Open this lesson's GitHub repository, github.com/ask-codey/exercise-powershell-the-bike-shops-monthly-report, 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:
exports: October's sales from the till (sales-2026-10.csv) and October's rentals from the booking app (rentals-2026-10.csv), as exported. Don't change them.shop-settings.json: the shop's settings, which you change in step 4.README.md: the five steps.Check.Tests.ps1: the check that reads the files you make. You don't need to change it.
What to do
Open PowerShell 7 in the exercise folder, the one with Check.Tests.ps1 in it, and look at the sales first:
Import-Csv exports\sales-2026-10.csv | Select-Object -First 6 | Format-Table
Date Store Item Qty Price
---- ----- ---- --- -----
2026-10-01 Downtown Helmet 2 $39.99
2026-10-02 Hillcrest Inner Tube 5 $6.99
2026-10-03 Riverside Bike Lock 1 $24.50
2026-10-04 Downtown City Bike 1 $449.00
2026-10-06 Hillcrest Bike Light 2 $18.75
Prices with dollar signs, a blank row, and a column called Item where the rest of the shop's files say Product. The rentals export is no tidier. Run every command from the exercise folder, and save every new file there too, beside Check.Tests.ps1, not inside exports. The five steps, also in the folder's README.md:
- Clean the sales.
clean-sales.csv, with the columnsDate,Store,Product,Qtyas a whole number,Priceas a plain number, and a newTotal, the Qty times the Price. No blank rows. - Totals per product.
product-totals.csv, from the clean sales:Product,SoldandRevenue, the biggest Revenue first. - Rental income per store.
rental-income.csv, from the rentals export:Store,RentalsandIncome, the biggest Income first. - Change a setting. In
shop-settings.json, the report's month goes from2026-09to2026-10, with everything else kept as it was. Copy the file toshop-settings.backup.jsonfirst. - The report.
report.html, a web page of the product totals with the headingMaple Street Bikes: October 2026and a<style>block of your own.
Check your work
The check uses Pester, PowerShell's test tool. If you haven't installed it yet, install it once:
Install-PSResource Pester
Then, from the exercise folder:
Invoke-Pester
At first all five tests fail. That's expected: each step you finish turns its test green. A failing test says which step it is, what it expected and what it found.
Made a mistake? Run the step again: Export-Csv and Set-Content replace what a file held. If shop-settings.json went wrong, copy your backup over it. To start over, download the exercise again.
If PowerShell says the check can't be loaded because running scripts is disabled or the file 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. Why a Script Won't Run: The Execution Policy, in Module 5, explains what that does.
Hints
- Look before you save. End each pipeline with
Format-Tableuntil the screen shows the right rows, then swap it forExport-Csv, as Saving Data: Export-Csv and Cleaning a CSV does. - Dollar signs out, numbers in.
.Replace('