4

The documentation for ForEach-object says "When you use the InputObject parameter with ForEach-Object, instead of piping command results to ForEach-Object, the InputObject value is treated as a single object." This behavior can easily be observed directly:

PS C:\WINDOWS\system32> ForEach-Object -InputObject @(1, 2, 3) {write-host $_}
1 2 3

This seems weird. What is the point of a "ForEach" if there is no "each" to do "for" on? Is there really no way to get ForEach-object to act directly on the individual elements of an array without piping? if not, it seems that ForEach-Object with InputObject is completely useless. Is there something I don't understand about that?

NewSites
  • 1,402
  • 2
  • 11
  • 26
  • You should also take a look at [about_Foreach](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_foreach?view=powershell-6) as suggested in the Notes section of the ForEach-Object documentation. – notjustme Oct 28 '19 at 10:50
  • 1
    Commands usually use a process block to loop through all the inputobject items, which requires a pipe. They would have to add an extra foreach loop to loop through all the inputobject items on the command line. I don't know the source code here and why that wasn't done. – js2010 Oct 28 '19 at 12:27
  • while i have absolutely nothing to back it up, i thot it was a side effect of generating a common scaffolding for the cmdlets. i don't think there is any other reason for it. have you opened an issue over at the PoSh github site? – Lee_Dailey Oct 28 '19 at 13:09
  • Similar questions have been asked about sort-object: https://stackoverflow.com/questions/35289705/how-does-inputobject-parameter-of-the-sort-object-cmdlet-work – js2010 Oct 28 '19 at 14:13
  • I'm not sure that this should be considered an issue that needs fixing, [it's documented behavior](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pipelines?view=powershell-6#one-at-a-time-processing) and it makes sense why it works this way. – codewario Oct 29 '19 at 15:59
  • 2
    @Lee_Dailey: There is a GitHub issue: https://github.com/PowerShell/PowerShell/issues/4242 – mklement0 Nov 07 '19 at 13:36

2 Answers2

4

In the case of ForEach-Object, or any cmdlet designed to operate on a collection, using the
-InputObject as a direct parameter doesn't make sense because the cmdlet is designed to operate on a collection, which needs to be unrolled and processed one element at a time. However, I would also not call the parameter "useless" because it still needs to be defined so it can be set to allow input via the pipeline.


Why is it this way?

-InputObject is, by convention, a generic parameter name for what should be considered to be pipeline input. It's a parameter with [Parameter(ValueFromPipeline = $true)] set to it, and as such is better suited to take input from the pipeline rather passed as a direct argument. The main drawback of passing it in as a direct argument is that the collection is not guaranteed to be unwrapped, and may exhibit some other behavior that may not be intended. From the about_pipelines page linked to above:

When you pipe multiple objects to a command, PowerShell sends the objects to the command one at a time. When you use a command parameter, the objects are sent as a single array object. This minor difference has significant consequences.

To explain the above quote in different words, passing in a collection (e.g. an array or a list) through the pipeline will automatically unroll the collection and pass it to the next command in the pipeline one at a time. The cmdlet does not unroll -InputObject itself, the data is delivered one element at a time. This is why you might see problems when passing a collection to the -InputObject parameter directly - because the cmdlet is probably not designed to unroll a collection itself, it expects each collection element to be handed to it in a piecemeal fashion.

Consider the following example:

# Array of hashes with a common key
$myHash = @{name = 'Alex'}, @{name='Bob'}, @{name = 'Sarah'}

# This works as intended
$myHash | Where-Object { $_.name -match 'alex' }

The above code outputs the following as expected:

Name                           Value
----                           -----
name                           Alex

But if you pass the hash as InputArgument directly like this:

Where-Object -InputObject $myHash { $_.name -match 'alex' }

It returns the whole collection, because -InputObject was never unrolled as it is when passed in via the pipeline, but in this context $_.name -match 'alex' still returns true. In other words, when providing a collection as a direct parameter to -InputObject, it's treated as a single object rather than executing each time against each element in the collection. This can also give the appearance of working as expected when checking for a false condition against that data set:

Where-Object -InputObject $myHash { $_.name -match 'frodo' }

which ends up returning nothing, because even in this context frodo is not the value of any of the name keys in the collection of hashes.


In short, if something expects the input to be passed in as pipeline input, it's usually, if not always, a safer bet to do it that way, especially when passing in a collection. However, if you are working with a non-collection, then there is likely no issue if you opt to use the -InputObject parameter directly.

codewario
  • 19,553
  • 20
  • 90
  • 159
  • I get what you're saying, but would please say whether (a) for the `ForEach-object` cmdlet, the `InputObject` parameter is useless, or (b) there is some instance in which it serves a useful purpose? – NewSites Oct 29 '19 at 20:24
  • If you are working with a non-collection object then there shouldn't be any issue with using the `-InputObject` parameter if you are more comfortable doing so. – codewario Oct 29 '19 at 20:36
  • Why would I ever use `ForEach-object` with a non-collection object? – NewSites Oct 30 '19 at 00:45
  • You wouldn't. My statement is about the `InputObject` parameter itself, it's not unique to `ForEach-Object`. – codewario Oct 30 '19 at 01:57
  • Okay, so my question was whether the `InputObject` parameter is useless in `ForEach-object`. It seems that your answer confirms that it is indeed useless without saying so explicitly. Is there a reason you don't want to say that it's useless? Is there any useful purpose it might ever be good for? – NewSites Oct 30 '19 at 12:00
  • In the case of `ForEach-Object`, or any cmdlet designed to operate on a collection, using the parameter directly doesn't make sense because the cmdlet is designed to operate on a collection, which needs to be unrolled and processed one element at a time. However, I would also not call the parameter "useless" because it still needs to be defined so it can be set to allow input via the pipeline. – codewario Oct 30 '19 at 12:24
  • I updated the answer to address `ForEach-Object` specifically first, then continue on to the rest of the answer. – codewario Oct 30 '19 at 13:47
  • 1
    Thank you. I've accepted the answer and appreciate the detailed explanation. I interpret your update to the answer to mean that the parameter is necessary for the operation of the cmdlet, but from the point of view of the programmer, it serves no purpose and there is no reason to ever use it. – NewSites Oct 30 '19 at 14:09
3

Bender the Greatest's helpful answer explains the current behavior well.

For the vast majority of cmdlets, direct use of the -InputObject parameter is indeed pointless and the parameter should be considered an implementation detail whose sole purpose is to facilitate pipeline input.

There are exceptions, however, such as the Get-Member cmdlet, where direct use of
-InputObject allows you to inspect the type of a collection itself, whereas providing that collection via the pipeline would report information about its elements' types.

Given how things currently work, it is quite unfortunate that the -InputObject features so prominently in most cmdlets' help topics, alongside "real" parameters, and does not frame the issue with enough clarity (as of this writing): The description should clearly convey the message "Don't use this parameter directly, use the pipeline instead".

This GitHub issue provides an categorized overview of which cmdlets process direct
-InputObject arguments how
.


Taking a step back:

While technically a breaking change, it would make sense for -InputObject parameters (or any pipeline-binding parameter) to by default accept and enumerate collections even when they're passed by direct argument rather than via the pipeline, in a manner that is transparent to the implementing command.

This would put direct-argument input on par with pipeline input, with the added benefit of the former resulting in faster processing of already-in-memory collections.

mklement0
  • 382,024
  • 64
  • 607
  • 775