Capstone: the sanctuary feeding report
Combine CSV import, grouping, hashtables, advanced functions and formatting into a real reporting tool.
- Turn a raw CSV log into typed, summarized objects
- Compare results against limits stored in a hashtable
- Package the logic as an advanced function that returns an object, and format it at the edge
A note pinned to the feeding-room door:
Whoever is the new automation keeper: every evening I add up the feeding log with a pencil to see who’s been overfed. Vesper keeps getting seconds. Please make the computer do it. - Head Keeper Ada
The log is a CSV file with one row per meal:
1Time,Dragon,Food,Kg
207:00,Ember,goats,20
307:30,Glim,moths,1
4...and every dragon has a daily limit in kilograms. By the end, your script prints:
1=== EMBERFALL FEEDING REPORT ===
2Meals: 9 (07:00-18:30)
3Total: 155 kg
4Hungriest: Vesper (54 kg)
5Over limit: Ember, Vesper
6Status: ALERTStep 1: load it and check the types
ConvertFrom-Csv (or Import-Csv for a file) gives you objects - but every property is a string. Check with Get-Member before trusting any arithmetic:
1$log = @'
2Time,Dragon,Food,Kg
307:00,Ember,goats,20
411:00,Ember,goats,22
507:30,Glim,moths,1
6'@ | ConvertFrom-Csv
7$log.Count
8($log | Get-Member -Name Kg).Definition
9($log | Measure-Object -Property Kg -Sum).Sum3 string Kg=20 43
Measure-Object converts strings to numbers when it adds them, so sums are safe. Sorting and comparing are not - convert with [int] or a calculated property first.
Step 2: one row per dragon
Group-Object Dragon collects each dragon’s meals in the group’s Group property. Turn each group into a tidy summary object with [pscustomobject] - objects keep the data usable for the next step, whereas formatted text would be a dead end.
1$log = @'
2Time,Dragon,Food,Kg
307:00,Ember,goats,20
411:00,Ember,goats,22
507:30,Glim,moths,1
6'@ | ConvertFrom-Csv
7$perDragon = $log | Group-Object Dragon | ForEach-Object {
8 [pscustomobject]@{
9 Dragon = $_.Name
10 Meals = $_.Count
11 Kg = ($_.Group | Measure-Object -Property Kg -Sum).Sum
12 }
13}
14$perDragon | Sort-Object Kg -Descending | ForEach-Object { '{0}: {1} kg in {2}' -f $_.Dragon, $_.Kg, $_.Meals }Ember: 42 kg in 2 Glim: 1 kg in 1
Step 3: compare against limits
A hashtable is the natural home for per-dragon limits: $limits[$dragon] looks one up instantly. Guard against dragons that have no limit yet with .ContainsKey() - visitors arrive all the time.
1$limits = @{ Ember = 45; Glim = 2 }
2$totals = @{ Ember = 52; Glim = 1; Nimbus = 70 }
3foreach ($dragon in $totals.Keys | Sort-Object) {
4 if (-not $limits.ContainsKey($dragon)) {
5 "$dragon has no limit yet"
6 } elseif ($totals[$dragon] -gt $limits[$dragon]) {
7 '{0} is {1} kg over' -f $dragon, ($totals[$dragon] - $limits[$dragon])
8 } else {
9 "$dragon is fine"
10 }
11}Ember is 7 kg over Glim is fine Nimbus has no limit yet
Step 4: objects in, objects out
The final tool is an advanced function that takes meals from the pipeline and returns one report object. Formatting it into text happens outside the function. That separation pays off: the same function can feed a console report today, ConvertTo-Json for a dashboard tomorrow, and a Pester test the day after.
Import-Csv feeding.csv | Get-FeedingReport -Limit $limits | ConvertTo-JsonCollect the pipeline objects in process, and do all the summarizing in end, once everything has arrived.
Where to go next
- Schedule it: register the script with Task Scheduler (or cron) to run every evening at 20:00.
- Send it: post the report to a chat channel with
Invoke-RestMethod -Method Postand a webhook URL, or save it as JSON for a dashboard. - Test it: write Pester tests for
Get-FeedingReportwith tiny fake logs. - Go further: the Az and Microsoft.Graph modules bring the same skills to Azure and Microsoft 365;
Invoke-Commandbrings them to every server you manage.
Key takeaways
Check types with Get-Member; convert text to numbers before sorting or comparing.
Group-Object plus
[pscustomobject]turns raw rows into one summary per thing.Hashtables hold lookups like limits; guard missing keys with
.ContainsKey().Return objects from functions and format at the edge - the same data can then go anywhere.
Lesson quiz
6 questions · pass with 5 correct · up to 50 XP
Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.
Practice: write PowerShell scripts
Write a script in the editor and run it for real against sample input, which is piped into your script as $input. Scripts run on PowerShell 6.2 through Try It Online (tio.run), a free public service, so these exercises avoid PowerShell 7-only syntax; your script and test input are sent there.
Meals per dragon
The input is the feeding log CSV. Print one line per dragon, most kilograms first: Vesper: 54 kg (2 meals) - and (1 meal), singular, when there was only one.
- A busy day
- A quiet day
Lessons teach PowerShell 7; your script runs on PowerShell 6.2 via Try It Online (tio.run), a free public service, so stick to syntax that works there. Test input is piped into your script as $input. Your script and test input are sent to that service.
Over the limit
The input is the feeding log CSV; the starter defines $limits. For each dragon that ate more than its limit, in alphabetical order, print Ember ate 52 kg - 7 kg over the limit of 45. Dragons with no limit are never over. If nobody is over, print Everyone ate sensibly.
- A busy day
- A quiet day
- A visitor
Lessons teach PowerShell 7; your script runs on PowerShell 6.2 via Try It Online (tio.run), a free public service, so stick to syntax that works there. Test input is piped into your script as $input. Your script and test input are sent to that service.
The feeding report
Write the advanced function Get-FeedingReport. It takes meals from the pipeline (-Meal) and a -Limit hashtable, and returns one object with these properties:
Meals(the count),FirstandLast(the first and last meal times, in log order)TotalKg, andHungriest- an object with theDragonandKgof the dragon that ate the mostOverLimit- the alphabetical names of the dragons over their limit (an empty array if none)
The starter formats your object into the report - keep that part as it is:
1=== EMBERFALL FEEDING REPORT ===
2Meals: 9 (07:00-18:30)
3Total: 155 kg
4Hungriest: Vesper (54 kg)
5Over limit: Ember, Vesper
6Status: ALERT- A busy day
- A quiet day
- A visitor
Lessons teach PowerShell 7; your script runs on PowerShell 6.2 via Try It Online (tio.run), a free public service, so stick to syntax that works there. Test input is piped into your script as $input. Your script and test input are sent to that service.
Questions about this lesson
Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.
Loading posts…