In PowerShell, everything is an object. While traditional command shells like Bash or Command Prompt (cmd.exe) pass data between programs as raw streams of text characters, PowerShell passes structured .NET objects through its pipeline (|). This architectural difference eliminates the need for fragile text parsing, regular expressions, or column trimming to extract system data.
Everything Is an Object
An object is a self-contained programming entity that combines:
- Properties: Characteristics, attributes, or data fields that describe the object (e.g., a process's
Name,Id,CPU, orMemory). - Methods: Actions or operations that the object knows how to perform on itself (e.g.,
.Kill(),.Start(),.Refresh()). - Type: The underlying .NET class blueprint that defines its structure (e.g.,
System.Diagnostics.Process).
Process Object
│
├── Properties
│ ├── Id
│ ├── ProcessName
│ ├── CPU
│ └── WorkingSet
│
└── Methods
├── Kill()
├── Start()
├── Refresh()
└── WaitForExit()Every piece of data in PowerShell, be it a number, a string, a process, a file, or even the output of a cmdlet, is an instance of a .NET class, carrying its own properties (data) and methods (actions).
$file = Get-Item "C:\Windows\notepad.exe"
$file.GetType().FullNameSystem.IO.FileInfowhere:
Get-Itemdoesn't return a plain string path; it returns a fullFileInfoobject.- That object exposes properties like
.Length,.LastWriteTime, and.Extensiondirectly, with no text parsing required.
When you run a cmdlet like Get-Process, PowerShell does not return text formatted into columns. It creates and emits in-memory objects representing each process. The console formatting you see is generated by the engine at the very end of execution for human readability.
Passing Objects vs. Text
Traditional shells output ASCII/UTF-8 character streams. Extracting a specific value (such as a PID) requires string processing tools like awk, cut, sed, or findstr. If a vendor changes a CLI tool's column width or spacing, scripts written for that output break immediately.
Traditional Text Pipeline:
[Command A] === "Text String Output" ===> [Parser (grep/awk)] === "Extracted Text" ===> [Command B]
PowerShell Object Pipeline:
[Cmdlet A] === [Typed Object Reference] ===> [Cmdlet B] (Accesses $object.Property directly)Text-Based vs. Object-Based Comparison
| Characteristic | Traditional Text Pipeline (Batch / Bash) | PowerShell Object Pipeline |
|---|---|---|
| Data Format | Unstructured bytes/strings | Strongly-typed .NET object references |
| Data Extraction | String indexing, regular expressions, awk, cut |
Direct property reference ($object.Property) |
| Fragility | High (breaks when output spacing or locale changes) | Low (property names remain static across environments) |
| Capabilities | Display and text manipulation only | Actions can be executed directly via methods |
| Performance Overhead | Parsing strings repeatedly into variables | Minimal overhead; structured objects passed directly |
Get-ChildItem | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) }where $_.LastWriteTime is a real DateTime object, so it can be compared directly with another DateTime using -gt, without converting strings to dates manually.
The Pipeline Operator (|)
The pipeline operator (|) sends the output of the command on its left directly to the input of the command on its right.
Unlike shells that wait for a command to finish completely before sending its accumulated text downstream, PowerShell streams objects one at a time through the pipeline. As soon as Get-Process discovers its first process object, that single object travels down the pipeline immediately.
Get-Process | Where-Object { $_.CPU -gt 10 } | Sort-Object CPU -Descendingwhere:
Get-Processemits a stream ofProcessobjects.Where-Objectfilters that stream based on theCPUproperty.Sort-Objectreorders the filtered stream by theCPUproperty, without ever converting the data to text.
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
125 105.62 292.73 87.58 3636 1 explorer
32 28.52 89.10 31.08 1592 1 WindowsTerminal
106 118.99 103.15 17.88 6440 1 SearchApp
20 7.29 30.92 13.20 7132 1 RuntimeBroker
30 9.87 36.50 11.03 5744 1 notepad
44 92.33 109.69 10.58 2528 1 powershellA simpler example demonstrating object pass-through:
Get-Process -Name "explorer" | Stop-Process -WhatIfWhat if: Performing the operation "Stop-Process" on target "explorer (4912)".Pipeline Parameter Binding
When an object travels through the pipeline, the receiving cmdlet automatically determines how to bind the incoming object to its parameters using one of two methods:
- ByValue: The cmdlet binds incoming data based on the object's .NET type. If a cmdlet accepts a
Processobject on a parameter markedByValue, passing aProcessobject satisfies it directly. - ByPropertyName: The cmdlet inspects the incoming object for a property whose name matches the target parameter name. For example, if an incoming object has a property called
.Name, a downstream cmdlet with a-Nameparameter accepts that property automatically.
Inspecting Objects with Get-Member
To discover what properties and methods are available on an object, pipe it into the Get-Member cmdlet (alias: gm). It reveals the type name, properties, and methods available on any object, making it the primary tool for discovering what you can do with pipeline output.
Get-Service -Name "wuauserv" | Get-Member TypeName: System.ServiceProcess.ServiceController
Name MemberType Definition
---- ---------- ----------
Name AliasProperty Name = ServiceName
Close Method void Close()
Pause Method void Pause()
Refresh Method void Refresh()
Start Method void Start()
Stop Method void Stop()
CanPauseAndContinue Property bool CanPauseAndContinue {get;}
CanShutdown Property bool CanShutdown {get;}
CanStop Property bool CanStop {get;}
DisplayName Property string DisplayName {get;set;}
ServiceName Property string ServiceName {get;set;}
Status Property System.ServiceProcess.ServiceControllerStatus Status {get;}Filtering Members
To isolate readable attributes or callable actions, use the -MemberType parameter:
Get-Date | Get-Member -MemberType Property TypeName: System.DateTime
Name MemberType Definition
---- ---------- ----------
Day Property int Day {get;}
DayOfWeek Property System.DayOfWeek DayOfWeek {get;}
Hour Property int Hour {get;}
Month Property int Month {get;}
Year Property int Year {get;}where:
-MemberType Propertyfilters the output to show only data-holding properties, excluding methods.- Omitting
-MemberTypeshows both properties and methods together.
You can also filter for methods only:
Get-Process -Name "pwsh" | Get-Member -MemberType Property
Get-Process -Name "pwsh" | Get-Member -MemberType MethodOnce you identify an available property or method, access it directly using dot notation:
(Get-Service -Name "wuauserv").Status
$calc = Start-Process -FilePath "calc.exe" -PassThru
$calc.WaitForExit(1000)
$calc.Kill()Another example using a DateTime method:
(Get-Date).AddDays(5)Saturday, March 19, 2026 8:30:15 AMCustom Objects with [PSCustomObject]
While built-in cmdlets return predefined object types, you'll often need to build your own structured objects. For example, you may need to combine data from multiple sources into a single report row. The [PSCustomObject] type accelerator is the standard tool for this.
A [PSCustomObject] is created by casting an inline hash table literal (@{}):
$report = [PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
DiskFree = (Get-Volume -DriveLetter C).SizeRemaining
CheckedAt = Get-Date
}
$reportwhere:
[PSCustomObject]@{ ... }builds a new object with named properties, preserving the order they're defined in.- Each key in the hashtable literal becomes a strongly accessible property (
$report.DiskFree), not just a text label.
./custom-report.ps1ComputerName DiskFree CheckedAt
------------ -------- ---------
WEB01 123456789012 9/27/2026 9:35:57 PMA more elaborate example combining system information:
$ServerReport = [PSCustomObject]@{
ServerName = $env:COMPUTERNAME
OSVersion = [System.Environment]::OSVersion.VersionString
LogicalCPUs = [System.Environment]::ProcessorCount
AuditDate = Get-Date -Format "yyyy-MM-dd"
}
$ServerReport | Format-Listwhere:
[PSCustomObject]@{ ... }converts the hashtable into a typed, ordered object.[System.Environment]accesses native .NET static system runtime information.$ServerReportfunctions like any native PowerShell object and can be piped toExport-Csv,ConvertTo-Json, orFormat-Table.
pwsh ./custom-object-demo.ps1ServerName : WORKSTATION-01
OSVersion : Microsoft Windows NT 10.0.22631.0
LogicalCPUs : 16
AuditDate : 2026-09-27Custom objects can also be collected into an array and piped into other cmdlets like any built-in object type:
$results = @(
[PSCustomObject]@{ Name = "WEB01"; Status = "Online" }
[PSCustomObject]@{ Name = "WEB02"; Status = "Offline" }
)
$results | Where-Object { $_.Status -eq "Online" } | Format-TableName Status
---- ------
WEB01 OnlinePractical Example: Pipelined System Audit
The following script queries services, extracts related process resource data, creates structured [PSCustomObject] records, and pipes the resulting stream to sort and format:
Get-CimInstance -ClassName Win32_Service -Filter "State='Running' and ProcessId > 0" |
Select-Object -First 5 |
ForEach-Object {
$Process = Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
[PSCustomObject]@{
ServiceName = $_.Name
PID = $_.ProcessId
DisplayName = $_.DisplayName
MemoryMB = if ($Process) { [math]::Round($Process.WorkingSet64 / 1MB, 2) } else { 0 }
Responding = if ($Process) { $Process.Responding } else { $false }
}
} |
Sort-Object -Property MemoryMB -Descendingwhere:
Get-CimInstance -ClassName Win32_Servicequeries running Windows services via WMI/CIM.Select-Object -First 5limits pipeline execution to the first five objects for demonstration.ForEach-Objectruns a script block for each incoming service object.[math]::Round($Process.WorkingSet64 / 1MB, 2)calculates real memory in megabytes using .NET math.- The pipeline passes custom objects directly into
Sort-Object -Property MemoryMB -Descendingwithout converting them into intermediate files or strings.
Run the script from the console:
pwsh ./audit-service-processes.ps1ServiceName : Audiosrv
PID : 2064
DisplayName : Windows Audio
MemoryMB : 13.04
Responding : True
ServiceName : Appinfo
PID : 7228
DisplayName : Application Information
MemoryMB : 12.47
Responding : True
ServiceName : AppHostSvc
PID : 3232
DisplayName : Application Host Helper Service
MemoryMB : 8.18
Responding : True
ServiceName : AppXSvc
PID : 2760
DisplayName : AppX Deployment Service (AppXSVC)
MemoryMB : 7.79
Responding : True
ServiceName : AudioEndpointBuilder
PID : 1672
DisplayName : Windows Audio Endpoint Builder
MemoryMB : 7.29
Responding : True