Skip to content
CogniSkill

LESSON 2 OF 4

Build a pipeline without losing the evidence

Practice notesBooks for further learning

A pipeline passes objects

Create a fixture collection:

$services = @(
    [pscustomobject]@{ Host='APP01'; Service='BackupAgent'; Status='Running'; UptimeHours=12 }
    [pscustomobject]@{ Host='APP02'; Service='BackupAgent'; Status='Stopped'; UptimeHours=0 }
    [pscustomobject]@{ Host='APP03'; Service='BackupAgent'; Status='Unknown'; UptimeHours=$null }
)
$services | Where-Object Status -eq 'Stopped' | Select-Object Host, Service
$services | Group-Object Status | Select-Object Name, Count

The first pipeline emits one row for APP02. The second emits three groups with one item each. The input objects remain unchanged. Filtering a report is different from modifying the source or remediating a service.

Where-Object makes a decision about each input object. Select-Object chooses the properties to emit. Group-Object changes the grain: instead of one item per service observation, you get one group per distinct status. Explain this change before adding a total to your report. A count of observations is not automatically a count of unique hosts.

If one host appears twice in a file, counting rows overstates the host population. Our first report will preserve duplicate observations, rather than silently remove them. The project extension requires a documented identity and duplicate policy.

The single-item collection trap

PowerShell can unwrap a command's single result. Use an array subexpression when the rest of your code requires a collection:

$stopped = @($services | Where-Object Status -eq 'Stopped')
$stopped.Count
$none = @($services | Where-Object Status -eq 'Missing')
$none.Count

Expected counts are 1 and 0. @(...) gives the downstream code a consistent collection shape when the command produces zero, one or several objects. That reduces fragile branching around .Count and indexing.

An empty result is not always good news. A completed, authorized search finding no stopped services differs from a collection that failed before returning data. The report should carry collection status when connected to a real source. For the offline fixture, an empty file is an invalid input because it cannot demonstrate the expected observations.

Formatting belongs at the end of a display pipeline

A formatting command emits instructions for the display system. It does not preserve the original object contract for further data processing. This is a common reporting defect:

$services | Select-Object Host, Service, Status | Format-Table
$services | Select-Object Host, Service, Status | ConvertTo-Csv -NoTypeInformation

The first output is suitable for a human looking at the terminal. The second output is CSV text with the requested column names. Do not insert Format-Table before Export-Csv; the export would receive formatting objects rather than service observations. A report that looks correct on screen can still have an unusable file.

Compare the CSV header with your predicted schema. It should name Host, Service, and Status. Open the resulting text rather than assuming the export is correct because the console looked tidy.

Functions make a decision independently testable

Separate the classification rule from file input and output. A function should return a small object representing its result. Avoid sending progress narration into the success output stream: a string saying “Working…” would become an extra pipeline item. Use a separate diagnostic stream for information a reader needs but a downstream CSV parser should not receive.

Our function will emit exactly one object per observation. It will use Assessment values Review, NoFinding, or Invalid. These names describe a reporting decision. NoFinding does not prove that a machine is healthy; it means that this rule found no reportable condition in the supplied fields.

Trace the boundary manually

The rule is: a stopped service needs review; a running service with uptime below 2 hours needs review; other valid running rows have no finding. Write expected results for uptime 0, 1, 2 and 3 before reading the implementation. At exactly 2 hours, a running service must have NoFinding because “below 2” is strict. A deliberate change from -lt to -le should break your boundary test.

An unknown service status remains invalid. It is not healthy and it is not proof of a stopped service. Keeping that distinction makes the report useful for troubleshooting the collector as well as the endpoint.

Evidence laboratory — attempt before reading the solution

Synthetic disk rows: A SizeGB=100 FreeGB=9; B SizeGB=200 FreeGB=20; C SizeGB=0 FreeGB=0; D SizeGB=100 FreeGB=blank. Rule: alert only when valid free percentage is strictly below 10. Compute expected states before coding. Design an object with Name, FreePercent and State. Then write a function that validates required fields, performs numeric arithmetic and emits one object per input. Do not query or alter real disks for this exercise.

Official reference for further study

YOUR NEXT STEP

Keep building your skills

No directly related CogniSkill book is mapped to this course yet. Continue with the free learning, or compare the publishing catalogue by your next goal.

Explore books by topic and goal →

Sign in to save lesson completion. Reading position is separate from completion.

Practice notes — your working space

Save predictions, outputs and explanations as you work. Notes stay on this browser, not your account; people sharing this browser can access them. Avoid passwords and sensitive data. Export to keep a portable copy, or clear your notes before leaving a shared device.

How was this lesson?

Help us improve this lesson.