10 · Project — System Info Reporter¶
Time to combine everything from Level 1 — variables, control flow, functions, the object pipeline, arrays/hashtables, files, error handling, and a module — into one working script: a system info reporter that gathers basic facts about the machine it's run on and writes a clean report to both the console and a JSON file.
Goal¶
Build SystemReport.ps1 that:
- Collects OS, PowerShell version, CPU/memory, and top processes by memory.
- Formats the results as a friendly console report.
- Handles missing/unavailable data gracefully (some info differs across Windows/macOS/Linux).
- Exports the full report as JSON for later use (e.g. by a monitoring dashboard).
- Is organized as a small module (
SystemInfo.psm1) plus a thin entry-point script that calls it — mirroring how real production automation is structured.
Step 1 — the module: SystemInfo.psm1¶
# SystemInfo.psm1
function Get-BasicSystemInfo {
[CmdletBinding()]
param()
try {
$os = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
} catch {
$os = "Unknown"
}
[pscustomobject]@{
OSDescription = $os
PSVersion = $PSVersionTable.PSVersion.ToString()
MachineName = $env:COMPUTERNAME ? $env:COMPUTERNAME : (hostname)
ProcessorCount = [Environment]::ProcessorCount
GeneratedAt = (Get-Date).ToString("s")
}
}
function Get-TopMemoryProcesses {
[CmdletBinding()]
param(
[int]$Top = 5
)
Get-Process |
Sort-Object WorkingSet64 -Descending |
Select-Object -First $Top -Property ProcessName, Id,
@{Name = "MemoryMB"; Expression = { [math]::Round($_.WorkingSet64 / 1MB, 1) }}
}
function Get-SystemReport {
[CmdletBinding()]
param(
[int]$TopProcessCount = 5
)
$report = [ordered]@{
SystemInfo = Get-BasicSystemInfo
TopProcesses = @(Get-TopMemoryProcesses -Top $TopProcessCount)
}
return $report
}
Export-ModuleMember -Function Get-BasicSystemInfo, Get-TopMemoryProcesses, Get-SystemReport
A few things worth noting:
[pscustomobject]@{...}builds a lightweight, ordered custom object — the idiomatic PowerShell way to return structured data instead of a plain hashtable, because it prints as a clean table and keeps property order.- The
?:-style ternary ($env:COMPUTERNAME ? ... : ...) is a PowerShell 7+ feature; onpwsh7.0+ it works out of the box. - Wrapping OS detection in
try/catchmeans the script degrades gracefully instead of crashing on a platform where a given API isn't available.
Step 2 — the entry-point script: SystemReport.ps1¶
# SystemReport.ps1
param(
[string]$OutputPath = "system-report.json",
[int]$TopProcessCount = 5
)
Import-Module (Join-Path $PSScriptRoot "SystemInfo.psm1") -Force
try {
$report = Get-SystemReport -TopProcessCount $TopProcessCount
} catch {
Write-Output "Failed to build system report: $($_.Exception.Message)"
exit 1
}
Write-Output "=== System Info ==="
$report.SystemInfo | Format-List
Write-Output "`n=== Top $TopProcessCount Processes by Memory ==="
$report.TopProcesses | Format-Table -AutoSize
try {
$report | ConvertTo-Json -Depth 4 | Set-Content -Path $OutputPath
Write-Output "`nReport written to $OutputPath"
} catch {
Write-Output "Failed to write report file: $($_.Exception.Message)"
exit 1
}
$PSScriptRoot is an automatic variable holding the directory the currently
running script lives in — using it to build the module path means the
script works no matter what directory you invoke it from.
Step 3 — running it¶
=== System Info ===
OSDescription : macOS 14.5
PSVersion : 7.4.1
MachineName : adas-macbook
ProcessorCount : 10
GeneratedAt : 2026-07-20T09:15:03
=== Top 5 Processes by Memory ===
ProcessName Id MemoryMB
----------- -- --------
chrome 41022 892.3
pwsh 1234 145.7
Finder 5678 88.9
Dock 9012 52.1
Spotlight 3344 47.6
Report written to system-report.json
Step 4 — verifying the JSON output¶
OSDescription : macOS 14.5
PSVersion : 7.4.1
MachineName : adas-macbook
ProcessorCount : 10
GeneratedAt : 2026-07-20T09:15:03
Where to take it from here¶
- Add a
-Formatparameter that switches between console output, JSON, and CSV usingswitch. - Add disk-space info with
Get-PSDrive -PSProvider FileSystem. - Wrap the whole thing to run on a schedule (Level 2/3 territory —
Scheduled Tasks & Automation). - Add Pester tests for
Get-BasicSystemInfoandGet-TopMemoryProcesses(Level 2's testing module).
How It Actually Works¶
This project stitches together the two abstractions the whole level has
been building toward: the module system's child-session-state isolation,
and the object pipeline's ability to carry structured .NET data all the
way to serialization. When SystemReport.ps1 calls
Get-SystemInfo, it isn't receiving text — it's receiving a live
PSCustomObject (built by [PSCustomObject]@{...}, which the engine
compiles into an ordered-property PSObject with no backing .NET type of
its own, purely an ETS-defined shape) that still carries full type
fidelity: nested hashtables, numbers as numbers, dates as DateTime
instances.
That fidelity is exactly what makes ConvertTo-Json meaningful rather
than a glorified string formatter: it walks the object graph via
reflection/ETS member enumeration, recursing into properties up to
-Depth levels, and serializes each .NET primitive to its correct JSON
type (DateTime becomes an ISO-8601 string, bool becomes true/
false, nested PSCustomObject/hashtable becomes a nested JSON object).
Contrast this with Out-File, which would have routed the same object
through the formatting subsystem instead and produced a column-truncated
text table — the reporter works because it stays on the "structured
object" side of the pipeline until the very last step, converting to text
only at genuine output time.
Sourcing the logic from a .psm1 rather than inlining it in the script
also means the module's internal helper functions (anything not
Export-ModuleMember'd) are invisible to SystemReport.ps1's own scope —
the entry-point script can only see the deliberately exported surface,
which is the module boundary doing real isolation work, not just
organizational tidiness.
🔀 See this in another language¶
- Python — Project — CLI To-Do App
- C# — Project — Console Task Manager App
- Kotlin — Project — CLI To-Do App
Exercise¶
Extend SystemInfo.psm1 with a new function Get-DiskSummary that uses
Get-PSDrive -PSProvider FileSystem to report each drive's name, used space,
and free space (in GB, rounded to 1 decimal), export it from the module, and
add its output as a third section (DiskInfo) in both the console report
and the JSON file produced by SystemReport.ps1.