Project 1: System Information Dashboard

Build a PowerShell dashboard script that inventories a machine and produces both a clean console summary and a rich HTML report you could hand to a helpdesk lead.

Quick Reference

Attribute Value
Difficulty Beginner (Level 1)
Time Estimate 6-10 hours
Main Programming Language PowerShell 7 (Alternatives: Windows PowerShell 5.1)
Alternative Programming Languages Bash + system tools, Python (limited for Windows-specific data)
Coolness Level Level 2: Useful utility
Business Potential Level 3: Support tooling / IT ops reporting
Prerequisites Basic CLI usage, variables/loops, basic Windows/Linux system concepts
Key Topics Object pipeline, CIM/WMI, data shaping, reporting/formatting

1. Learning Objectives

By completing this project, you will:

  1. Enumerate system information using CIM/WMI and other native cmdlets.
  2. Inspect and reshape object output to a stable report schema.
  3. Generate consistent console and HTML output from the same object model.
  4. Build deterministic output modes for repeatable reporting.
  5. Package a script with parameters, validation, and exit codes.

2. All Theory Needed (Per-Concept Breakdown)

2.1 PowerShell Objects, Types, and Pipeline Binding

Fundamentals

PowerShell is not a text shell; it is an object pipeline. Every Get-* cmdlet emits .NET objects with typed properties and methods. These objects flow through the pipeline one at a time, and downstream cmdlets bind to them by type or by matching property names. That is why Get-Process | Stop-Process works without you explicitly passing a process ID: the pipeline binder matches the incoming System.Diagnostics.Process objects to the -InputObject parameter. When you build reports, you rely on predictable object shapes. This makes Get-Member and Select-Object foundational: Get-Member tells you what properties exist, and Select-Object lets you create the exact fields your report needs. Once you internalize that the pipeline moves objects, you stop parsing strings and start composing transformations.

Deep Dive into the Concept

The pipeline engine is the real “runtime” of PowerShell. It performs three steps for each pipeline segment: it enumerates upstream output, it binds input to downstream parameters, and it invokes the command for each object or chunk. Enumeration matters because collections are unrolled; this is why Get-Process produces one object at a time. Binding matters because PowerShell resolves the best parameter match using metadata: parameters may accept pipeline input by value (type match) or by property name (a property on the input object matches a parameter name). When you are building a dashboard, you want to normalize objects so they bind consistently later. For example, if you build a [PSCustomObject] with properties ComputerName, OSVersion, and UptimeDays, you can pass it into an export cmdlet, a formatter, or a serializer and get predictable behavior. If the downstream cmdlet expects a different property name, you can add a calculated property to bridge the gap.

PowerShell’s Extended Type System (ETS) adds another layer: it can attach additional properties or format data to otherwise plain .NET objects. This is why a Get-Process object appears to have properties like CPU even though the underlying .NET type doesn’t expose it directly. ETS also controls default formatting views. For reporting, you must be careful not to insert formatting cmdlets (Format-Table, Format-List) in the middle of the pipeline because they replace objects with formatting directives. Once formatting happens, you lose the ability to sort or export cleanly. The safe sequence is: acquire objects, shape them, then format or export at the end.

To build robust tooling, you also need to understand pipeline behavior with errors. Some cmdlets emit non-terminating errors that you can trap with -ErrorAction or $ErrorActionPreference. When you build a report, you should decide whether missing data results in blank fields, error records, or a hard stop. A common pattern is to wrap each data source in try/catch, then return a report object even if one data source fails, using null values or explicit error fields. This preserves the pipeline contract and keeps output consistent.

Finally, remember that object shapes are a design decision. The script should emit a single “report object” with a stable schema. That object can then be rendered to console, converted to HTML, or exported to JSON/CSV without re-querying the system. This “one model, many views” approach is the core of professional PowerShell tooling and is a pattern you will reuse in almost every project in this guide.

How this Fits on Projects

This project uses pipeline objects to collect data sources and then reshape them into a single report schema. The same schema feeds console output and HTML output.

Definitions & Key Terms

  • Object pipeline -> Stream of .NET objects passed between cmdlets.
  • ByValue binding -> Pipeline binds input based on compatible types.
  • ByPropertyName binding -> Pipeline binds input when a property name matches a parameter.
  • Extended Type System (ETS) -> PowerShell layer that adds properties and formatting metadata to objects.
  • Report schema -> The agreed set of properties that your tool guarantees.

Mental Model Diagram (ASCII)

Get-* cmdlets -> objects -> Select-Object -> report object -> output views
     |                         |                    |
     |                     add/rename               +-- HTML
     |                     properties               +-- Console
     +-- typed data                                  +-- JSON/CSV

How It Works (Step-by-Step)

  1. A Get-* cmdlet emits typed objects.
  2. The pipeline enumerates objects one at a time.
  3. Select-Object reshapes them into a report schema.
  4. Downstream output cmdlets consume that schema.
  5. Final formatting/serialization happens at the end.

Minimal Concrete Example

$os = Get-CimInstance Win32_OperatingSystem
[PSCustomObject]@{
  ComputerName = $env:COMPUTERNAME
  OSVersion    = $os.Caption
  UptimeDays   = ((Get-Date) - $os.LastBootUpTime).TotalDays
} | Format-List

Common Misconceptions

  • “PowerShell pipelines pass strings.” -> They pass objects unless you use native commands.
  • “Format-Table is safe in the middle.” -> It destroys object fidelity.
  • “If it prints right, it’s right.” -> Formatting can hide missing fields.

Check-Your-Understanding Questions

  1. Why does Format-Table break later pipeline steps?
  2. What is the difference between ByValue and ByPropertyName binding?
  3. How would you add a calculated UptimeDays property to an object?

Check-Your-Understanding Answers

  1. It converts objects to formatting directives, which are not the original objects.
  2. ByValue matches types; ByPropertyName matches property names to parameter names.
  3. Use Select-Object @{Name='UptimeDays';Expression={ [math]::Round(((Get-Date) - $_.LastBootUpTime).TotalDays, 2) }} or build a custom object.

Real-World Applications

  • Inventory scripts that emit structured JSON for CMDB ingestion.
  • Health checks that produce standardized reports across servers.

Where You’ll Apply It

References

  • Microsoft Learn: about_Objects
  • Microsoft Learn: about_Pipelines

Key Insights

A stable report object is the backbone that lets one script power many outputs.

Summary

PowerShell’s object pipeline lets you build a single report model and render it in multiple formats. The key is to shape objects early and format only at the end.

Homework/Exercises to Practice the Concept

  1. Take Get-Service output and produce a custom object with only Name, Status, and StartType.
  2. Prove that Format-Table breaks Export-Csv by testing both orders.
  3. Build a tiny report object with three custom properties.

Solutions to the Homework/Exercises

  1. Get-Service | Select-Object Name, Status, StartType.
  2. Get-Service | Format-Table | Export-Csv produces unusable CSV; Select-Object first works.
  3. @{Name='X';Value=1;Note='Demo'} wrapped in [PSCustomObject].

2.2 CIM/WMI as a System Information Source

Fundamentals

Windows exposes system inventory through WMI (Windows Management Instrumentation) and its modern interface, CIM. PowerShell’s Get-CimInstance cmdlet queries these classes and returns strongly typed objects for hardware, OS, disk, and network information. Instead of parsing the output of systeminfo.exe, you can query Win32_OperatingSystem, Win32_ComputerSystem, Win32_Processor, and Win32_LogicalDisk to retrieve structured data. CIM uses a consistent model and can target local or remote machines, which makes it ideal for building inventory tools. Understanding which classes map to which data and how to compute derived values (like uptime or memory usage percentage) is essential for a reliable dashboard.

Deep Dive into the Concept

CIM (Common Information Model) is a standardized schema for representing systems, and WMI is Microsoft’s implementation. Historically, PowerShell used Get-WmiObject, which relied on DCOM. Modern PowerShell uses Get-CimInstance, which speaks WS-Man by default and provides better remoting, concurrency, and error handling. CIM classes are organized into namespaces like root\cimv2. Each class exposes properties, methods, and associations. For example, Win32_OperatingSystem exposes Caption, Version, LastBootUpTime, and TotalVisibleMemorySize. Win32_ComputerSystem reveals manufacturer, model, and total physical memory. Win32_LogicalDisk provides size and free space, but only for logical volumes, which means you must filter by DriveType (3 = local disk) to avoid CD-ROM or network shares.

A dashboard needs derived metrics, not just raw fields. Uptime is a good example: CIM returns LastBootUpTime as a datetime; you subtract it from Get-Date and then format to days or hours. Memory usage typically uses TotalVisibleMemorySize and FreePhysicalMemory, which are in kilobytes. Disk usage requires computing FreeSpace / Size. These conversions are critical because they allow you to communicate user-friendly data. A report that shows “16381952 KB” is less valuable than “15.6 / 31.7 GB (49% used).” This conversion is part of your report schema and should be consistent across outputs.

CIM also comes with pitfalls. Not every system exposes the same classes (especially non-Windows systems), and some classes require elevated permissions. You should design your script to handle missing classes gracefully. Use -ErrorAction Stop inside try/catch and return partial data if a query fails. For performance, group queries: a single Get-CimInstance call per class is often fine, but repeated queries for each property are not. If you need multiple properties, request them in one query and reuse the object.

Another important concept is remoting. Get-CimInstance can target remote computers directly with -ComputerName, and it can accept -CimSession objects for repeated queries. Even if this first project is local, build your code so it could accept a -ComputerName parameter later. That design choice makes the script extensible into Project 4’s remoting tool.

Finally, understand that CIM data is authoritative but not always “live.” Some classes are cached or may be updated on a schedule. A dashboard should therefore include a timestamp and a clear unit label. If you add a “freeze time” option for deterministic runs, you should explicitly note that the data is frozen for repeatability. This helps when comparing reports across machines or over time.

How this Fits on Projects

This project uses CIM classes to build the system inventory sections. The same CIM data source pattern is used again in Project 4 for remote health checks.

Definitions & Key Terms

  • CIM -> Common Information Model, a standardized system schema.
  • WMI -> Windows Management Instrumentation, Microsoft’s implementation of CIM.
  • Namespace -> Logical grouping of CIM classes (e.g., root\cimv2).
  • CimSession -> A reusable session for multiple CIM queries.
  • DriveType -> Numeric code indicating disk type (3 = local disk).

Mental Model Diagram (ASCII)

[PowerShell] -> Get-CimInstance -> [CIM Class] -> [Typed Object]
     |                |                |
     |                |                +-- Properties (Size, FreeSpace)
     |                +-- Namespace (root\cimv2)
     +-- Local or remote target

How It Works (Step-by-Step)

  1. Choose a CIM class (e.g., Win32_OperatingSystem).
  2. Query it with Get-CimInstance.
  3. Read properties from the returned object.
  4. Convert units and compute derived metrics.
  5. Store results in the report schema.

Minimal Concrete Example

$os = Get-CimInstance -ClassName Win32_OperatingSystem
$uptime = (Get-Date) - $os.LastBootUpTime
[PSCustomObject]@{
  OS     = $os.Caption
  Uptime = [math]::Round($uptime.TotalDays, 2)
}

Common Misconceptions

  • “WMI is deprecated.” -> Get-WmiObject is legacy; CIM is the modern replacement.
  • “CIM data is always current.” -> Some values are refreshed on intervals.
  • “All classes exist everywhere.” -> Some classes are OS-specific or require admin rights.

Check-Your-Understanding Questions

  1. Why is Get-CimInstance preferred over Get-WmiObject?
  2. Which class would you query for disk free space?
  3. How do you convert FreePhysicalMemory to GB?

Check-Your-Understanding Answers

  1. It uses WS-Man, supports sessions, and is the modern API.
  2. Win32_LogicalDisk with DriveType -eq 3.
  3. Divide by 1MB (1024*1024) and round.

Real-World Applications

  • Asset inventory reports for IT compliance.
  • Capacity planning dashboards for server fleets.

Where You’ll Apply It

References

  • Microsoft Learn: Get-CimInstance
  • Microsoft Learn: Win32_OperatingSystem, Win32_LogicalDisk

Key Insights

CIM provides reliable, structured system data without brittle parsing.

Summary

CIM/WMI is the authoritative, structured source for Windows system information. Query once, compute derived values, and cache results in your report object.

Homework/Exercises to Practice the Concept

  1. Query Win32_ComputerSystem and list the manufacturer and model.
  2. List all logical disks and filter to local fixed drives.
  3. Compute total and free memory in GB.

Solutions to the Homework/Exercises

  1. Get-CimInstance Win32_ComputerSystem | Select-Object Manufacturer, Model.
  2. Get-CimInstance Win32_LogicalDisk | Where-Object DriveType -eq 3.
  3. Use TotalVisibleMemorySize and FreePhysicalMemory divided by 1MB.

2.3 Formatting, Reporting, and HTML Output

Fundamentals

PowerShell separates object data from presentation. Formatting cmdlets (Format-Table, Format-List) control how data looks in the console, while output cmdlets (Out-File, Export-Csv, ConvertTo-Html) transform data into files or other formats. A professional tool builds one report object and then renders multiple views. HTML reporting is a common requirement because it creates a shareable, printable artifact for managers or tickets. When you use ConvertTo-Html, you generate a table from objects, and you can embed additional sections such as system metadata, timestamps, or warnings. The key is to avoid formatting too early so that the same object can be used for console and HTML.

Deep Dive into the Concept

PowerShell’s formatting system is view-based: when you output an object to the console, PowerShell chooses a default view (table, list, or wide) based on the object type and formatting metadata stored in .format.ps1xml files. This default view is often optimized for humans, not for reports. When you call Format-Table, you override the view explicitly, but you also convert objects into formatting instructions that cannot be re-serialized. This is why reporting requires two tracks: the data track and the presentation track. You should keep the data track as objects and only apply formatting at the final step.

ConvertTo-Html is the built-in bridge between objects and a shareable report. It accepts objects and turns them into HTML tables. You can customize the title, include pre- and post-content, and add CSS with -Head. A practical approach is to build a small HTML template with a <style> block and then insert multiple tables: a summary table (the core report object), and a detail table for disks or network adapters. Because ConvertTo-Html produces predictable HTML, you can also post-process the output to add classes or styling.

Reporting also requires consistent units and labels. If you show MemoryGB in one place and Memory in another, the report becomes ambiguous. Decide on a schema early: e.g., MemoryGB_Used, MemoryGB_Total, DiskC_GB_Free. Then format those values in the console with Format-Table and emit them as raw numbers in HTML. Some teams prefer rounding in the object; others prefer rounding in the view. The safe path is to store both raw and display values, or store raw values and round at render time. For this beginner project, storing rounded values directly is acceptable, but document that decision in the report.

A robust report also includes metadata: report timestamp, hostname, script version, and data source. If output depends on time, you should allow a -AsOf or -FreezeTime parameter that forces a deterministic timestamp for testing. This makes your golden output repeatable, which is important for regression tests and for comparing outputs in a course. You can also include a -Format parameter to select console-only vs HTML-only modes, but the default should generate both to make the tool useful to non-technical readers.

Finally, remember that HTML output is a security boundary. If you include untrusted data, you should HTML-encode it to avoid injection. For system inventory, this is less of a risk, but it matters if you include user input or network names. PowerShell’s System.Web.HttpUtility::HtmlEncode() is available in Windows PowerShell; in PowerShell 7 you can use [System.Net.WebUtility]::HtmlEncode().

How this Fits on Projects

The dashboard renders a console summary and an HTML report from the same report object. Later projects use the same pattern for logs and health checks.

Definitions & Key Terms

  • Formatting cmdlet -> Controls console view (Format-Table, Format-List).
  • Output cmdlet -> Writes or converts data (Out-File, Export-Csv, ConvertTo-Html).
  • View -> Default rendering for a type in PowerShell.
  • HTML report -> Shareable artifact built from object data.
  • Deterministic output -> Fixed timestamp/seed for repeatable results.

Mental Model Diagram (ASCII)

Report Object
   |\
   | \-> Format-Table -> Console
   |
   +--> ConvertTo-Html -> report.html -> Browser

How It Works (Step-by-Step)

  1. Build a report object with stable property names.
  2. Format the object for console output (table or list).
  3. Convert the same object to HTML with a template.
  4. Save HTML to disk with Out-File.
  5. Optionally open the report in a browser.

Minimal Concrete Example

$report = [PSCustomObject]@{
  ComputerName = $env:COMPUTERNAME
  OSVersion    = (Get-CimInstance Win32_OperatingSystem).Caption
  ReportTime   = (Get-Date)
}
$report | Format-List
$report | ConvertTo-Html -Title 'System Report' | Out-File .\report.html

Common Misconceptions

  • “Formatting and exporting are the same.” -> Formatting is for display; exporting is for data.
  • “HTML reports are just tables.” -> You can include summaries, warnings, and metadata.
  • “Console output must match HTML exactly.” -> The schema should match; the display can differ.

Check-Your-Understanding Questions

  1. Why should formatting happen after object shaping?
  2. What does ConvertTo-Html consume: strings or objects?
  3. How do you make a report deterministic for testing?

Check-Your-Understanding Answers

  1. Formatting destroys object fidelity and breaks later transforms.
  2. It consumes objects and converts them into tables.
  3. Add a -AsOf or -FreezeTime parameter and use it instead of Get-Date.

Real-World Applications

  • Email-ready HTML health reports for IT teams.
  • Automatic compliance reports attached to tickets.

Where You’ll Apply It

References

  • Microsoft Learn: ConvertTo-Html
  • Microsoft Learn: about_Format.ps1xml

Key Insights

Separate data from presentation to keep reports reusable and testable.

Summary

Formatting is the final step. Keep a clean report object and render it into console or HTML views at the end.

Homework/Exercises to Practice the Concept

  1. Build a one-row report object and render it to console and HTML.
  2. Add CSS styling to ConvertTo-Html output.
  3. Implement a -AsOf parameter and use it to freeze time.

Solutions to the Homework/Exercises

  1. Use [PSCustomObject] and Format-List plus ConvertTo-Html.
  2. Use -Head "<style>table{border-collapse:collapse;}</style>".
  3. param([datetime]$AsOf=(Get-Date)) and use $AsOf instead of Get-Date.

3. Project Specification

3.1 What You Will Build

A PowerShell script named Get-SystemReport.ps1 that:

  • Collects OS, CPU, memory, disk, uptime, and network information.
  • Builds a single report object with a stable schema.
  • Prints a clean console summary.
  • Generates an HTML report with metadata and a summary table.
  • Supports deterministic output via an -AsOf parameter.

Included: local system inventory, console output, HTML output, CSV/JSON optional export. Excluded: remote inventory (covered in Project 4), live charts, GUI.

3.2 Functional Requirements

  1. System inventory: gather OS caption, version, hostname, CPU model, memory totals, disk totals, primary IPv4, and uptime in days.
  2. Report schema: output a [PSCustomObject] with documented property names.
  3. Console output: print a readable summary (table or list) without truncation.
  4. HTML output: generate report.html with title, timestamp, and summary table.
  5. Deterministic mode: accept -AsOf to freeze report time and uptime calculation.
  6. Exit codes: return 0 on success, 2 when a required data source fails, 3 for invalid parameters.

3.3 Non-Functional Requirements

  • Performance: complete in under 2 seconds on a typical workstation.
  • Reliability: if a single data source fails, emit partial data and set exit code.
  • Usability: clear parameter help and example usage.

3.4 Example Usage / Output

PS> .\Get-SystemReport.ps1 -HtmlPath .\report.html

ComputerName OSVersion                   UptimeDays CPU                            MemoryGB DiskC_GB IPv4
------------ ---------                   ---------- ---                            -------- ------- ----
LAB-PC01     Microsoft Windows 11 Pro    3.22       Intel(R) Core(TM) i7-10750H     31.77    475.9   192.168.1.25

HTML report written to .\report.html

3.5 Data Formats / Schemas / Protocols

Report object schema:

{
  ComputerName: string,
  OSVersion: string,
  OSBuild: string,
  UptimeDays: number,
  CPU: string,
  MemoryGB_Total: number,
  MemoryGB_Used: number,
  DiskC_GB_Total: number,
  DiskC_GB_Free: number,
  IPv4: string,
  ReportTime: datetime,
  DataErrors: string[]
}

3.6 Edge Cases

  • CIM class missing or unavailable (return nulls, add to DataErrors, exit code 2).
  • Machine has no IPv4 (leave field blank, add warning).
  • Disk C: not present (use first fixed disk instead).
  • Running in PowerShell 7 on Linux (skip Windows-only CIM classes, warn).

3.7 Real World Outcome

This is what a user should see when the tool works correctly.

3.7.1 How to Run (Copy/Paste)

pwsh .\Get-SystemReport.ps1 -HtmlPath .\report.html -AsOf "2025-01-01T12:00:00Z"

3.7.2 Golden Path Demo (Deterministic)

  • -AsOf is set to 2025-01-01T12:00:00Z.
  • Script uses a fixed uptime seed of 3.22 days for the demo output.

3.7.3 CLI Terminal Transcript (Success)

$ pwsh .\Get-SystemReport.ps1 -HtmlPath .\report.html -AsOf "2025-01-01T12:00:00Z"

ComputerName OSVersion                UptimeDays CPU                          MemoryGB DiskC_GB IPv4
------------ ---------                ---------- ---                          -------- ------- ----
LAB-PC01     Microsoft Windows 11 Pro 3.22       Intel(R) Core(TM) i7-10750H   31.77    475.9   192.168.1.25

HTML report written to .\report.html
ExitCode: 0

3.7.4 CLI Terminal Transcript (Failure)

$ pwsh .\Get-SystemReport.ps1 -ForceCimFailure
WARNING: CIM query failed: Win32_OperatingSystem

ComputerName OSVersion UptimeDays CPU MemoryGB DiskC_GB IPv4
------------ --------- ---------- --- -------- ------- ----
LAB-PC01     (null)    (null)      N/A N/A      N/A     N/A

HTML report not written due to errors
ExitCode: 2

4. Solution Architecture

4.1 High-Level Design

[Get-SystemReport.ps1]
   |
   +--> [Data Sources]
   |       - CIM OS / CPU / Memory / Disk
   |       - Net adapter
   |
   +--> [Report Builder]
   |       - Schema + derived metrics
   |
   +--> [Renderers]
           - Console (Format-Table)
           - HTML (ConvertTo-Html)

4.2 Key Components

Component Responsibility Key Decisions
Data Collectors Query CIM and network cmdlets Use Get-CimInstance with error handling
Report Builder Normalize and compute metrics Single report schema to drive all outputs
Renderer Console + HTML output Format at the end to keep objects intact

4.3 Data Structures (No Full Code)

# Report object schema
[PSCustomObject]@{
  ComputerName    = $env:COMPUTERNAME
  OSVersion       = $os.Caption
  OSBuild         = $os.BuildNumber
  UptimeDays      = [math]::Round($uptime.TotalDays, 2)
  CPU             = $cpu.Name
  MemoryGB_Total  = [math]::Round($memTotalGb, 2)
  MemoryGB_Used   = [math]::Round($memUsedGb, 2)
  DiskC_GB_Total  = [math]::Round($diskTotalGb, 1)
  DiskC_GB_Free   = [math]::Round($diskFreeGb, 1)
  IPv4            = $ip
  ReportTime      = $AsOf
  DataErrors      = $errors
}

4.4 Algorithm Overview

Key Algorithm: System Report Assembly

  1. Query CIM classes for OS/CPU/Memory/Disk.
  2. Compute derived metrics and conversions.
  3. Build a report object with stable property names.
  4. Render output to console and HTML.

Complexity Analysis

  • Time: O(n) for number of CIM queries (constant small n).
  • Space: O(1) for report object.

5. Implementation Guide

5.1 Development Environment Setup

# Windows
winget install Microsoft.PowerShell
# Optional: VS Code + PowerShell extension

5.2 Project Structure

project-root/
+-- Get-SystemReport.ps1
+-- report.html
+-- tests/
|   +-- Get-SystemReport.Tests.ps1
+-- README.md

5.3 The Core Question You’re Answering

“How do I turn scattered system data into a single, reusable report object?”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. Object pipeline and Select-Object – Why object shape matters.
  2. CIM data sources – Which classes provide which fields.
  3. Formatting vs output – Why Format-Table must be last.

5.5 Questions to Guide Your Design

  1. What is the minimal schema that still answers real support questions?
  2. Where will you convert units and round values?
  3. How will the script behave if one CIM query fails?

5.6 Thinking Exercise

Write a small script that prints OS caption, uptime, and memory usage as one object.

5.7 The Interview Questions They’ll Ask

  1. Why is Get-CimInstance preferred over Get-WmiObject?
  2. How do you avoid breaking the pipeline with formatting?
  3. What is the difference between raw data and report schema?

5.8 Hints in Layers

Hint 1: Start with OS + uptime only, then add CPU, memory, and disk. Hint 2: Build a [PSCustomObject] before formatting. Hint 3: Add HTML output after console output works.

5.9 Books That Will Help

| Topic | Book | Chapter | |——|——|———| | PowerShell fundamentals | PowerShell in Action | Object pipeline chapters | | WMI/CIM usage | PowerShell Cookbook | CIM/WMI recipes | | Reporting | PowerShell Scripting and Toolmaking | Output and formatting |

5.10 Implementation Phases

Phase 1: Inventory Queries (2-3 hours)

  • Query CIM classes and print raw objects.
  • Confirm property names and units. Checkpoint: You can display OS, CPU, memory, disk.

Phase 2: Report Object (2-3 hours)

  • Build the report schema.
  • Convert units, add uptime, add errors array. Checkpoint: Format-List prints a complete report object.

Phase 3: Output Rendering (2-4 hours)

  • Console table and HTML report.
  • Add -AsOf deterministic option. Checkpoint: HTML file renders correctly and matches console data.

5.11 Key Implementation Decisions

| Decision | Options | Recommendation | Rationale | |———|———|—————-|———–| | Data source | CIM vs native commands | CIM | Structured objects + remoting ready | | Output schema | Flat vs nested | Flat | Easier to render and export | | Determinism | -AsOf parameter vs fixed time | -AsOf parameter | Allows testing and real runs |


6. Testing Strategy

6.1 Test Categories

| Category | Purpose | Examples | |———-|———|———-| | Unit | Validate conversions | Bytes to GB, uptime math | | Integration | Verify CIM queries | OS + CPU + memory pipeline | | Edge Case | Missing disk C: | Use first fixed disk |

6.2 Critical Test Cases

  1. Deterministic mode: -AsOf produces stable uptime values.
  2. Missing CIM class: returns partial data and exit code 2.
  3. HTML output: file exists and contains report fields.

6.3 Test Data

# Example expected fields
ComputerName, OSVersion, UptimeDays, CPU, MemoryGB_Total

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

| Pitfall | Symptom | Solution | |———|———|———-| | Formatting too early | Export/HTML broken | Build object first, format last | | Wrong units | Memory/disk too large | Convert KB/bytes to GB | | Missing permissions | CIM errors | Run elevated or handle errors |

7.2 Debugging Strategies

  • Use Get-Member to inspect CIM objects.
  • Add -Verbose output around each query.

7.3 Performance Traps

  • Re-querying CIM classes multiple times; cache results instead.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add JSON export (ConvertTo-Json).
  • Add a -NoHtml switch.

8.2 Intermediate Extensions

  • Add per-disk reporting for all fixed drives.
  • Add a warning section when disk free < 10%.

8.3 Advanced Extensions

  • Add remote mode with -ComputerName (preview Project 4).
  • Build a scheduled task to generate daily reports.

9. Real-World Connections

9.1 Industry Applications

  • Service desk triage: quick inventory report before troubleshooting.
  • Asset management: snapshot for CMDB updates.
  • PSInventory: inventory collection scripts for Windows fleets.
  • PowerShell Universal: uses similar reporting patterns.

9.3 Interview Relevance

  • Explain object pipeline and CIM usage.
  • Describe how you ensured deterministic output and error handling.

10. Resources

10.1 Essential Reading

  • PowerShell in Action – object pipeline and formatting chapters.
  • PowerShell Cookbook – CIM/WMI recipes.

10.2 Video Resources

  • “PowerShell Remoting and CIM” – Microsoft Learn video series.

10.3 Tools & Documentation

  • Get-CimInstance documentation (Microsoft Learn).
  • ConvertTo-Html documentation (Microsoft Learn).

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain why PowerShell pipelines pass objects.
  • I can describe CIM classes used for OS, CPU, and disks.
  • I can explain why formatting must be last.

11.2 Implementation

  • Script outputs a stable report object.
  • Console and HTML outputs match the same schema.
  • Deterministic mode works with -AsOf.

11.3 Growth

  • I can identify how to extend this to remote inventory.
  • I can explain this project in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion

  • Script runs locally and outputs console summary.
  • Report object includes all required fields.
  • Exit codes reflect success vs data-source failure.

Full Completion

  • All minimum criteria plus:
  • HTML report is generated and styled.
  • Deterministic mode with -AsOf is supported.

Excellence (Going Above & Beyond)

  • Add JSON export and per-disk table.
  • Add warnings for low disk or high memory usage.

13. Deep-Dive Addendum: Turning Inventory Into a Reliable Report

13.1 Data Normalization Strategy (Make It Comparable)

A system report is only useful if you can compare it across time and across machines. That means you should decide on a canonical data model and normalize every raw field into that model. For example, memory and disk sizes should be expressed in a consistent unit (GiB with two decimal places), and dates should be normalized to ISO 8601. Define a strict schema for your report object: which fields are required, which are optional, and what types they are. If a CIM query fails, you should still output the field with a null value and a companion error field. This makes downstream processing consistent and prevents data shape drift. Treat every data source as untrusted and wrap each one in its own try/catch so you can isolate failures. The end goal is a report object that can be compared across systems like a database row, not a one-off screen dump.

13.2 Determinism and Repeatability (Freeze the World)

Reports are often used to compare today with last week, so the same command should produce the same shape and ordering every time. Provide a switch like -FreezeTime or -AsOf that lets the user inject a fixed timestamp. When enabled, you should never call Get-Date directly; instead, use the injected timestamp for derived values like uptime or file age. Also define a stable sort order for any list output (disks sorted by drive letter, network adapters by name). This allows you to diff reports without noise. If you generate HTML, use a fixed template and predictable ID attributes so small changes are easy to spot. Determinism is what makes reports testable, auditable, and useful for long-term trending.

13.3 HTML Report Engineering (View Layer Discipline)

Treat HTML output as a view, not the data. Build the report object first, then pass that object into a rendering function. This function should never query the system; it only formats. Use a simple template with consistent sections: summary header, resource tables, and a footer with metadata. If you include charts, derive them from the report object, not direct system queries. For reliability, embed CSS in the HTML so the report can be opened offline. Consider a dual-mode report: a compact view for quick scanning and a detailed view with expandable sections. Keep IDs and class names stable so you can later add lightweight automation (like JS filters) without rewriting the report generator.

13.4 Cross-Platform Data Gaps (Design for Missing Fields)

If you run this script on Linux or macOS, many Windows CIM classes are not available. Your report schema should allow platform-specific sections and should annotate each section with its origin (CIM vs native commands). A good pattern is to include a Platform field and a CollectionNotes field that explains which sources were used. This prevents confusion when a field is blank because it does not exist on that platform. Design your dashboard to degrade gracefully: if a data source is missing, the report still runs and produces a meaningful result. That is the difference between a brittle script and a professional inventory tool.

13.5 Operational Checklist (How This Runs in Production)

Before you ship this tool, decide where it will run and how output will be stored. If output goes to a shared folder, ensure permissions are handled and file names are collision-safe (include hostname and timestamp). If output goes to a ticketing system or CMDB, use JSON or CSV and validate the schema. Add a -Verbose mode for diagnostics and a -Quiet mode for automation. Always return meaningful exit codes: 0 for success, 1 for partial data (non-terminating errors), and 2 for fatal failure. This makes the tool reliable in scheduled tasks or CI pipelines where output is machine-consumed.