Skip to content

PowerShell Objects and the Pipeline

How PowerShell's object-based pipeline eliminates text parsing, enabling direct property access, method execution, and robust automation compared to traditional shells like Bash or cmd.

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, or Memory).
  • 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).

Example of Object Type Inspectionpowershell
$file = Get-Item "C:\Windows\notepad.exe"
$file.GetType().FullName
Outputplaintext
System.IO.FileInfo

where:

  • Get-Item doesn't return a plain string path; it returns a full FileInfo object.
  • That object exposes properties like .Length, .LastWriteTime, and .Extension directly, 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
Example of Direct DateTime Comparisonpowershell
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.

Example of a Pipelinepowershell
Get-Process | Where-Object { $_.CPU -gt 10 } | Sort-Object CPU -Descending

where:

  • Get-Process emits a stream of Process objects.
  • Where-Object filters that stream based on the CPU property.
  • Sort-Object reorders the filtered stream by the CPU property, without ever converting the data to text.
Outputplaintext
 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 powershell

A simpler example demonstrating object pass-through:

Example of Object Streamingpowershell
Get-Process -Name "explorer" | Stop-Process -WhatIf
Outputplaintext
What 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:

  1. ByValue: The cmdlet binds incoming data based on the object's .NET type. If a cmdlet accepts a Process object on a parameter marked ByValue, passing a Process object satisfies it directly.
  2. 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 -Name parameter 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.

Example of Get-Member on a Servicepowershell
Get-Service -Name "wuauserv" | Get-Member
Outputplaintext
   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:

Example of Filtered Get-Memberpowershell
Get-Date | Get-Member -MemberType Property
Outputplaintext
   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 Property filters the output to show only data-holding properties, excluding methods.
  • Omitting -MemberType shows both properties and methods together.

You can also filter for methods only:

Example of Filtering Methodspowershell
Get-Process -Name "pwsh" | Get-Member -MemberType Property

Get-Process -Name "pwsh" | Get-Member -MemberType Method

Once you identify an available property or method, access it directly using dot notation:

Example of Accessing Properties and Methodspowershell
(Get-Service -Name "wuauserv").Status

$calc = Start-Process -FilePath "calc.exe" -PassThru
$calc.WaitForExit(1000)
$calc.Kill()

Another example using a DateTime method:

Example of Calling .AddDays()powershell
(Get-Date).AddDays(5)
Outputplaintext
Saturday, March 19, 2026 8:30:15 AM

Custom 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 (@{}):

custom-report.ps1powershell
$report = [PSCustomObject]@{
    ComputerName = $env:COMPUTERNAME
    DiskFree     = (Get-Volume -DriveLetter C).SizeRemaining
    CheckedAt    = Get-Date
}

$report

where:

  • [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.
Consolepowershell
./custom-report.ps1
Outputplaintext
ComputerName DiskFree      CheckedAt
------------ --------      ---------
WEB01        123456789012  9/27/2026 9:35:57 PM

A more elaborate example combining system information:

custom-object-demo.ps1powershell
$ServerReport = [PSCustomObject]@{
    ServerName  = $env:COMPUTERNAME
    OSVersion   = [System.Environment]::OSVersion.VersionString
    LogicalCPUs = [System.Environment]::ProcessorCount
    AuditDate   = Get-Date -Format "yyyy-MM-dd"
}

$ServerReport | Format-List

where:

  • [PSCustomObject]@{ ... } converts the hashtable into a typed, ordered object.
  • [System.Environment] accesses native .NET static system runtime information.
  • $ServerReport functions like any native PowerShell object and can be piped to Export-Csv, ConvertTo-Json, or Format-Table.
Consolepowershell
pwsh ./custom-object-demo.ps1
Outputplaintext
ServerName  : WORKSTATION-01
OSVersion   : Microsoft Windows NT 10.0.22631.0
LogicalCPUs : 16
AuditDate   : 2026-09-27

Custom objects can also be collected into an array and piped into other cmdlets like any built-in object type:

Example of Custom Objects in a Pipelinepowershell
$results = @(
    [PSCustomObject]@{ Name = "WEB01"; Status = "Online" }
    [PSCustomObject]@{ Name = "WEB02"; Status = "Offline" }
)

$results | Where-Object { $_.Status -eq "Online" } | Format-Table
Outputplaintext
Name  Status
----  ------
WEB01 Online

Practical 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:

audit-service-processes.ps1powershell
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 -Descending

where:

  • Get-CimInstance -ClassName Win32_Service queries running Windows services via WMI/CIM.
  • Select-Object -First 5 limits pipeline execution to the first five objects for demonstration.
  • ForEach-Object runs 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 -Descending without converting them into intermediate files or strings.

Run the script from the console:

Consolepowershell
pwsh ./audit-service-processes.ps1
Outputplaintext
ServiceName : 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
Navigation