Skip to content
AgileLogics
6 min read

Pipeline-Aware Functions

Prerequisites: Advanced functions

To accept pipeline input like a real cmdlet, a function needs ValueFromPipeline and a process block.

function ConvertTo-Celsius {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory, ValueFromPipeline)]
    [double]$Fahrenheit
  )
  begin   { Write-Verbose 'Starting conversion' }
  process { [math]::Round(($Fahrenheit - 32) * 5 / 9, 1) }
  end     { Write-Verbose 'Done' }
}

32, 68, 212 | ConvertTo-Celsius
ConvertTo-Celsius -Fahrenheit 98.6
0
20
100
37
  • begin runs once, before any input — setup.
  • process runs once per pipeline object, with the parameter set to that object.
  • end runs once, after all input — cleanup or final output.

Without a process block, the body runs as end, so the function only sees the last piped item — a classic bug.

Binding by property name

ValueFromPipelineByPropertyName binds a parameter to the property with the same name (or alias) on each piped object:

function Test-Port {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
    [Alias('Host')]
    [string]$ComputerName,

    [Parameter(ValueFromPipelineByPropertyName)]
    [int]$Port = 443
  )
  process {
    [pscustomobject]@{ ComputerName = $ComputerName; Port = $Port; Checked = $true }
  }
}

$targets = @(
  [pscustomobject]@{ Host = 'web01'; Port = 80 }
  [pscustomobject]@{ Host = 'db01';  Port = 5432 }
  [pscustomobject]@{ Host = 'api01' }
)
$targets | Test-Port

ComputerName Port Checked
------------ ---- -------
web01          80    True
db01         5432    True
api01         443    True

This is how Import-Csv servers.csv | Test-Port just works: CSV columns become properties that bind to parameters.

Accepting both arrays and pipeline input

function Get-Initial {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory, ValueFromPipeline)]
    [string[]]$Name
  )
  process {
    foreach ($n in $Name) { $n.Substring(0, 1).ToUpper() }
  }
}

(Get-Initial -Name ada, linus) -join ''
('grace', 'alan' | Get-Initial) -join ''
AL
GA

With pipeline input, $Name holds one item per process call; passed directly, it holds the whole array. The inner foreach handles both.

Streaming

Output each result from process as soon as it's ready — don't collect everything into a list and output it in end. Streaming keeps memory low and lets Select-Object -First 5 stop the pipeline early.