skip to content

In PowerShell, what actually travels between two commands joined by the pipe operator, and how does that differ from a pipeline in bash?

level: juniorimportance: must knowfreq 85%

answer

  1. objects, not bytes
  2. no re-parsing of columns
  3. properties and methods survive the pipe
  4. Get-Member shows what is flowing
  5. formatting happens last, at the console

basics

~20 s

PowerShell pipes live .NET objects rather than text. Each command hands the next typed objects with named properties and methods, so downstream commands read properties directly instead of re-parsing columns of formatted text the way bash pipelines must.

solid answer

~50 s

In bash the contract between commands is a byte stream, so the next command has to recover structure from text — that is largely why `awk`, `cut` and `sed` exist. PowerShell passes the objects themselves: `Get-Process` emits `System.Diagnostics.Process` instances, and `Where-Object CPU -gt 10` tests the real `CPU` property rather than a column position. The practical wins are that a value containing a space cannot break field splitting, types survive so a date compares as a date and a number as a number, and `Get-Member` will tell you exactly what any object in the pipe offers. The costs are real too: you pay .NET object construction on every item, native executables at the edges still emit plain strings, and the table you see on screen is produced by the formatting layer at the very end — it is not what was flowing through the pipe.

code

powershell · 5 lines
powershell
# The pipe hands over live objects, not rendered text
Get-Process | Where-Object CPU -gt 10 | Sort-Object CPU -Descending | Select-Object -First 5 Name, Id, CPU

# Ask what is actually in the pipeline
Get-Process | Get-Member -MemberType Property

go deeper

for a junior

Be able to say plainly that PowerShell pipes objects while bash pipes text, and show one filter that reads a property by name, such as Where-Object on CPU or Length.

for a middle

Explain that cmdlets emit .NET instances, that the on-screen table is produced by the formatting layer at the end, and use Get-Member to prove what type is flowing.

for a senior

Show judgment about the boundaries: native commands emit strings, formatting cmdlets end a pipeline, and data destined for a file must be serialized rather than captured as rendered text.

for a principal

Own the tradeoff. Structure removes a whole class of parsing bugs but costs per-object allocation and couples you to .NET; argue when a PowerShell pipeline is the right integration surface and when plain text is.

## Two different contracts A Unix shell pipeline is defined by one contract: a process writes bytes to standard output and the next process reads bytes from standard input. Nothing else crosses the boundary. Every program therefore renders its internal data into text for display, and every consumer must parse that text back into fields. That round trip is where `awk '{print $2}'`, `cut -d: -f1` and a lot of fragile quoting come from: structure the producer already had is thrown away at the pipe and guessed at on the other side. PowerShell changes the contract. A cmdlet is a .NET class that *emits objects*, and the pipe hands those object references straight to the next command in the same process. `Get-Process` does not print a table; it emits `System.Diagnostics.Process` instances. `Get-ChildItem` emits `System.IO.FileInfo` and `System.IO.DirectoryInfo`. The rendering you see happens only when nothing else consumes the objects and they fall out of the end of the pipeline into the default output path. ## What objects remove ```powershell Get-Process | Where-Object CPU -gt 10 | Sort-Object CPU -Descending | Select-Object -First 5 Name, Id, CPU ``` Nothing in that pipeline parses anything. `Where-Object` evaluates a comparison against the `CPU` property, `Sort-Object` sorts on the property's real numeric value, and `Select-Object` projects three named properties. In a text shell each of those steps needs to know a column index or a delimiter, and each breaks the day a value contains the delimiter. Three concrete consequences follow: - **No field-splitting bugs.** A file named `My Report.txt` is one property value, not two fields. - **Types survive.** A `DateTime` property compares chronologically, not lexically; a size compares as a number, so `Where-Object Length -gt 1MB` is meaningful. (`1MB` is real PowerShell numeric-literal syntax.) - **The data is discoverable at runtime.** `Get-Process | Get-Member` prints the type name plus every property and method. There is no equivalent for a text stream — you can only look at it and guess. ## What you see is not what flows The most common beginner error is believing the console table *is* the data. It is not. When objects reach the end of the pipeline they are passed to the formatting layer, which consults per-type formatting rules to decide which properties to show and whether to use a table or a list. `Get-Process` displays a handful of columns while the object carries dozens of properties; `Select-Object *` or `Format-List *` reveals the rest. This is also why `Format-Table` and its siblings must be last: they replace your objects with internal formatting-instruction objects (types in the `Microsoft.PowerShell.Commands.Internal.Format` namespace) that only the output cmdlets understand. If you want data in a file, serialize the objects — `Export-Csv`, `ConvertTo-Json`, `Export-Clixml` — rather than capturing the rendered text. ## Where the object model leaks PowerShell still lives in a world of ordinary executables. When you run `git status` or `ping` inside PowerShell, that program writes text, and PowerShell wraps it as `System.String` objects — one per output line. So this works: ```powershell git status --porcelain | Where-Object { $_ -like ' M *' } ``` but `$_` there is a string, not a structured object, and you are back to matching patterns. `$_` (also spelled `$PSItem`) is the automatic variable holding the current pipeline item. Some cmdlets deliberately re-introduce structure at that boundary: `Select-String` returns `MatchInfo` objects with `Line`, `LineNumber`, `Path` and `Matches` properties rather than raw text, which is why it composes better than a bare grep-style filter. ## What it costs Objects are not free. Every item is a live .NET object, so a pipeline over a very large stream pays allocation and per-item cmdlet overhead that a tight C program reading bytes does not. PowerShell pipelines are usually slower than the equivalent text pipeline on huge inputs, and a language-level `foreach` loop over an in-memory collection is faster still than the pipeline machinery. You also inherit .NET's shape: interop with the wider Unix tool ecosystem happens through strings at the edges, and objects only travel intact between PowerShell commands. The honest summary for an interview: PowerShell trades raw throughput and universality for structure that does not have to be re-derived at every stage, and the biggest adjustment coming from bash is to stop reading the screen and start asking `Get-Member` what you actually hold.

  • If everything is an object, why does `Get-Process | Out-File procs.txt` write a table that looks exactly like the console?
    Because `Out-File` is an output cmdlet: objects that reach it are handed to the formatting layer first and rendered to text using the type's display rules. The file receives the rendering, not the data. To keep the data, serialize instead — `Export-Csv`, `ConvertTo-Json` or `Export-Clixml` write the properties themselves.
  • How do you find out what properties and methods the objects in a pipeline actually have?
    Pipe them into `Get-Member`. It reports the .NET type name plus every property, script property and method, which is the authoritative answer — the console view is filtered by formatting rules and usually shows only a few properties. `Select-Object *` or `Format-List *` is a quicker way to see all values for one item.
  • What do you get when you pipe a native executable such as `git` into a PowerShell cmdlet?
    Plain strings — PowerShell captures the program's standard output and emits one `System.String` per line. There is no structure to bind to, so downstream you are matching patterns rather than reading properties. Cmdlets like `Select-String` help by turning matches back into `MatchInfo` objects with `Line`, `LineNumber` and `Path`.

saying these in an interview costs you the question

  • Says the pipeline carries text that PowerShell parses for you
  • Believes the console table is the data itself
  • Thinks Format-Table output can be piped into Export-Csv
  • Claims you still need cut or awk to split columns
  • Assumes objects always make the pipeline faster than text

context