Understanding Pipeline โ
Overview of pipeline in powershell:
- A cmdlet can have multiple parameters that accept pipeline input.
- Only one parameter can bind to a given pipeline object at a time.
- PowerShell prioritizes by value over by property name.
Pipeline Parameter Binding โ
Pipeline Input Strategy โ
There's two solution when a pipeline input comes in as a fallback:
- ByValue: the default strategy. Accepts when the coming object can be cast or converted to the target type of the parameter.
- ByPropertyName: accepts when the coming object has property name matched to any parameter name of the cmdlet.
By Value โ
By PropertyName โ
spps -Name (gci -File | foreach Name)
# is equivalent to the following
# because FileInfo has Name which matches to -Name parameter of spps cmdlet
gci -File | spps2
3
4
ByValue is always tried first, and then use ByPropertyName, or it finally throws. A parameter accepts pipeline input does not necessarily have both solutions, it can have at least one of them.
Pipeline Input as Enumerator โ
As we know, PowerShell can handle multiple objects from an enumerator from object that implements IEnumerable or IEnumerable<T>, or even duck typed with GetEnumerator.
While for types that are not linear collection, manually invoking GetEnumerator is required when being passed as pipeline input.
IDictionary<,>andIDictionary- HashTable has dynamic typing so we can't presume a uniformed calculation for our cmdlet
stringisIEnumerablebut we surely don't expect the auto enumeration.
This is simply because these types are more likely to be treated as a whole object, even when dictionaries are IEnumerable<KeyValuePair<,>>.
$table = @{ Name = 'foo'; Age = 18 }
($table | measure).Count # 1
($table.GetEnumerator() | measure).Count # 2 #2
3
Enumerate Pipeline Items โ
You can use $input to refer to the enumerator passed to the function. This is one way to access pipeline input items but with more control. Another option is use $_ to represent the current item in process block, this is way more limited but commonly used.
NOTE
$_ and $input are isolated, they don't affects each other.
$inputrepresents a enumerator for pipeline input inprocessblock.$inputrepresents the whole collection for pipeline input inendblock.$inputwill be consumed after being used once in eitherprocessorend. UseResetto get it back.- You can't use
$inputin bothprocessandend.
Access Current Item โ
We're not going to talk about
$_, it's fairly simple. All the quirks is about$input.
$input.Current is $null by default, you'll have to manually invoke MoveNext before you access Current in process block since it's not a while loop.
function Test {
begin {
$input -is [System.Collections.IEnumerator] # True
}
process {
# $input.Current before MoveNext in each iteration is always $null
# How weird!
$input.Current -eq $null # True because we haven't start the enumeration!
$input.MoveNext() | Out-Null
$input.Current -eq $null # False
}
}
1,2,3 | Test2
3
4
5
6
7
8
9
10
11
12
13
14
15
WARNING
Before you read the following content, please keep in mind that $input behaves slightly different from general IEnumerator for Reset.
$input itself is a wrapper of current item in process block, invoke Reset to get it back to current value.
function Test {
process {
}
}
1,2,3 | Test2
3
4
5
6
7
Implicit Pipeline Input โ
Function can accepts pipeline input without any specific parameter. Inside the process block, $_ represents the current object from the pipeline input.
function Test {
process {
$_ -is [System.IO.FileInfo] # True
}
}
gci -file | Test2
3
4
5
6
7
NOTE
$_ is only available in process block.
Explicit Pipeline Input โ
If you write a custom function that have one or more parameters accept pipeline input, what is going on inside?
- In
beginblock, there's no value assigned to eachByPropertyNameparameter, they remain default. - In
processblock, eachByPropertyNameparameter represents the current property value extracted from the current pipeline input object.
function Foo {
param (
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Name
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Length
)
begin {
$Name -eq [string]::Empty # True
$Length -eq 0 # True
}
process {
$Name # Name of current pipeline item
$Length # Length of current pipeline item
}
}
gci -file | Foo2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TIP
ByPropertyName parameter can also be a array type, it all depends the implementation you want, it behaves the same.