$input
(a.k.a. “dollar input” or “input variable”) is one of those esoteric parts of the PowerShell language that create a lot of confusion. In fact just today there was a discussion on how it actually works on the PowerShell MVP mailing list. We even had to read the documentation to figure it out. 😉
Anyways, basically $input
in an enumerator which provides access to the pipeline you have.
So basically if you have a function which sums up the elements from the pipeline you can have something like:
Function Sum { $sum = 0 $input | foreach { $sum += $_ } $sum } 1, 2, 3 | Sum
Easy. In fact if you have ever added any script actions or links in the PowerGUI admin console, this is basically what you use to get access to the selection from the central grid.
Now, there are a couple not so obvious gotchas here:
1. Enumerator is not an array
Suppose you want to know how many objects you get from the pipeline – a totally valid question. Maybe your function is supposed to only get one.
You just do $input.Count
and… get nothing. Such property does not exist. This is an enumerator and it simply does not have such a property.
OK, you say, let’s wrap it into an array and we’ll learn the size:
@($input).Count
This works… Kind of… If you modify our example above to:
Function Sum { "Number of elements: " + @($input).Count $sum = 0 $input | foreach { $sum += $_ } "Sum is: " + $sum } 1, 2, 3 | Sum
You get:
Number of elements: 3
Sum is: 0
The first line is correct – we had 3 elements. But why the heck is the sum 0 now?
Well, $input is an enumerator, and when you use it – you get to the next element. So once we used it to create a temporary array we got to its end. To fix it, simply reset it back:
Function Sum { "Number of elements: " + @($input).Count $input.Reset() $sum = 0 $input | foreach { $sum += $_ } "Sum is: " + $sum } 1, 2, 3 | Sum
Now we are good again:
Number of elements: 3
Sum is: 6
2. Just using $input holds the pipeline till all objects are collected
This second one was spotted by Per here. He tried using $input and noticed that his function did not get executed until the whole pipeline was processed (that is his function was not invoked for each element one by one, but rather for the whole collection) – read his post for details.
This happens because according to PowerShell help:
“In the Process block of a function, $input contains the object currently in the pipeline… If the function does not have a Process block, the value of $input is available to the End block, and it contains all of the input to the function.”
So basically the reason for the code above to process the whole collection (rather than go item by item) is that we did not have a process block inside the function, so if you care about item by item processing – go with the process block, if not – feel free to use $input.
And by the way, inside the process block just stick to $_ like this:
Function Sum { begin { $sum = 0 } process { $sum += $_ } end { $sum } } 1, 2, 3 | Sum
Using $input
inside process
is a hustle and Oisin promised a post on his blog on the reasons why. 😉
Tags: KB, Knowledge Base, Known Issues, PowerShell
I will have to say many thanks for providing a google result to this one!!!
Glad that I could help! 🙂
This saved my bacon, thanks!
something’s wrong with use of this variable when declaring a parameter with ValueFromRemainingArguments… I get an error that “The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input” – thoughts?
Hello,
Seems that I use Begin/Process/End blocks, @(Input).count doesn’t work any more. What I would like to do is use Begin/Process/End blocks and also use Write-Progress in Process block. That’s why I need @(Input).count to get the total number of the input objects. Could you please help me ? Thx
would love to know the answer too this too. All i could find was to add the pipeline item to an array in the process block and then do the work in the end block. Not great
Function Sum {
begin {
$AllTheSum = @()
} process {
$AllTheSum += $_
} end {
$AllTheSum.Count
$AllTheSum | ForEach {
Write-Progress -PercentComplete $Count/$AllTheSum.Count * 100
# something with $_
}
}
}
thx, move the write-progress to END block is what I found too 🙂
no choice.