Capstone: A Multi-System Automation & Reporting Tool
PowerShell Intermediate/Advanced
Chapter 12 · Capstone: A Multi-System Automation & Reporting Tool
Dana's own Get-OldFileReport, from the Fundamentals capstone, checked one folder on one machine. The team has since grown to a small fleet of servers, and Dana's been asked for something that scales to all of them at once, reports out automatically, and — since other people now depend on it — is actually tested rather than just trusted. Get-FleetHealthReport is that tool, and building it touches every chapter in this course.
The function starts as a real advanced function (Chapter 1) — [CmdletBinding(SupportsShouldProcess)], a mandatory, pipeline-bound, non-empty -ComputerName parameter. Before any server is ever queried, each name is checked against the team's own naming convention with a regex (Chapter 2) — a malformed entry is rejected immediately, with a clear warning, rather than discovered mid-query as a confusing connection failure.
ForEach-Object -Parallel (Chapter 4) launches a Get-CimInstance query (Chapter 6) against every validated server concurrently — and because CIM travels over the same WinRM infrastructure Chapter 3's own remoting established, no separate connection setup is needed beyond what Enable-PSRemoting already provides on each target. Results land in a ConcurrentBag, specifically avoiding Chapter 4's own isolated-runspace gotcha where a plain shared array would have silently stayed empty.
A quick verbose summary is assembled with StringBuilder (Chapter 5) rather than repeated string concatenation. The full report also gets written to a real Excel workbook via COM automation — with Chapter 5's own cleanup discipline applied without exception: .Quit() and Marshal.ReleaseComObject() sit inside a finally block, so no orphaned EXCEL.EXE is left behind even if writing the workbook fails partway through.
The same results, converted with ConvertTo-Json, get posted to a monitoring webhook with Invoke-RestMethod (Chapter 7) — wrapped in try/catch that needs no -ErrorAction Stop, since HTTP failures are terminating by default, reading $_.ErrorDetails.Message for the real reason if the post fails.
The Complete Function
The function moves into FleetHealth.psm1, with Export-ModuleMember and the manifest's own FunctionsToExport (Chapter 8) both restricted to just Get-FleetHealthReport — nothing internal leaks out as if it were part of the public API.
A Pester test (Chapter 10) confirms the naming-pattern rejection actually works, by mocking Get-CimInstance and asserting it's never called for a malformed name — the test suite never touches a real server:
A reference to the module, plus a short convenience wrapper, goes into $PROFILE.CurrentUserAllHosts (Chapter 11) — available in every host, every session, from here on:
Chapter Attribution
| Step | Chapter(s) applied |
|---|---|
| 1 — Validating names | Chapter 1 (CmdletBinding, ValidateNotNullOrEmpty, pipeline input), Chapter 2 (regex) |
| 2 — Querying in parallel | Chapter 3 (WinRM foundation CIM relies on), Chapter 4 (ForEach-Object -Parallel, ConcurrentBag), Chapter 6 (Get-CimInstance) |
| 3 — The report | Chapter 5 (StringBuilder, COM automation with proper cleanup) |
| 4 — Webhook reporting | Chapter 7 (Invoke-RestMethod, terminating HTTP errors, ErrorDetails) |
| 5 — Packaging | Chapter 8 (Export-ModuleMember, FunctionsToExport) |
| 6 — Testing | Chapter 10 (Describe/It, Mock, Should -Invoke) |
| 7 — Availability | Chapter 11 ($PROFILE.CurrentUserAllHosts) |
ConvertTo-Json) where crossing an actual network genuinely required it. That's the same throughline Fundamentals 1 opened this entire two-course series with, now carrying a tool that queries a fleet, reports automatically, and — because it's tested and packaged — other people can actually trust without reading its source first.
Get-FleetHealthReport reports on existing state, it doesn't enforce configuration, which is a genuinely different job DSC was built for, not an oversight. The worked example above is illustrative code, not a script run against real infrastructure in the writing of this chapter. And nothing here covers long-term monitoring-platform integration beyond the single webhook POST shown — a real alerting pipeline (thresholds, escalation, history) is its own, larger topic.
Hands-On Exercises
Explain why Get-FleetHealthReport checks each computer name against a regex before the ForEach-Object -Parallel block runs, rather than letting a malformed name simply fail whatever CIM query eventually tries it.
Explain why the Excel-writing block's .Quit() and ReleaseComObject() calls sit inside a finally block rather than just after the try. What could happen if they didn't?
Explain what the Pester test in Step 6 is actually verifying, and why mocking Get-CimInstance — rather than running the function against a real server — is the right way to test that specific piece of behavior.
Chapter 12 Quick Reference — Course Complete
- 7 build steps, 11 prior chapters — one real, multi-system tool, tested and packaged, built in the order a genuine advanced automation project would actually hit each concept
- This course's own throughline, closed out: real object fidelity preserved across parallel runspaces, remote queries, .NET automation, and a network API call — the same discipline Fundamentals 1 began, now proven at fleet scale
- Honest scope note: no DSC (a genuinely different job), illustrative code only, no full monitoring-platform integration
- The PowerShell Intermediate/Advanced course is now complete — 12/12 chapters