Why does invoking a Powershell script block with .Invoke() return a collection?

It seems that invoking a Powershell script block (by invoking the .Invoke() method) always produces a collection. Specifically, a collection of type

System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]

Even invoking an empty script block ({}.Invoke()) returns a collection. Invoking the same script block using the call operator (&) produces the normally expected return (either a scalar or [object[]]).

This turns out to be convenient if you need a collection instead of an array, but it seems kind of counterintuitive.

Does anyone know why it behaves this way?

Edit:

I knew there are two different invocations, .Invoke() and .InvokeReturnAsIs() from reading the language spec. That's were I first noticed it.

I just don't understand the reasoning behind the naming convention and the way the mechanics of it appear to work. Looking at the documentation, what I would have thought would be the default invocation method is not what is used when the scriptblock is invoked in Powershell. It appears that .InvokeReturnAsIs() just returns a stream of objects, and then Powershell wraps it into an object array if there's more than one object, as scalar if there's only one object, or creates a null object if there are none, as if there's an implicit pipeline there. Using .Invoke() returns a collection, always, and Powershell leaves it as a collection.

Answers


Looks to be the difference between these two methods:

Invoke - Invokes the script block with the specified arguments, returning the results as PSObject objects.

InvokeReturnAsIs - Runs the script block with the specified arguments. This method returns the raw (unwrapped) result objects so that it can be more efficient.

http://msdn.microsoft.com/en-us/library/system.management.automation.scriptblock_methods(v=vs.85).aspx

Invoke

$code = {"a"}
$code.Invoke().Gettype().FullName

Output:

System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]

InvokeReturnAsIs

$code.InvokeReturnAsIs().GetType().FullName

Output:

System.String

My guess is that the team wanted to be consistent with the PowerShell.Invoke() API, which returns a Collection<PSObject>. This C# signature makes it easier for clients to consume 0, 1 or N returned values and not have to worry about checking for null and whether the returned object was wrapped or not.

From the .NET Design Guidelines:

DO NOT return null values from collection properties or from methods returning collections. Return an empty collection or an empty array instead.

You could say then why not just return object. Then I would have to against null or not and then I'd have to test to see if it implemented ICollection to determine if I had a scalar or collection. From a C# dev's point of view, this is a spew (pardon the rhyme). :-)


Because that is what it is designed to do, and there is an alternate!

Invoke - Invokes the script block with the specified arguments, returning the results as (collection of) PSObject objects.

InvokeReturnAsIs - Runs the script block with the specified arguments. This method returns the raw (unwrapped) result objects so that it can be more efficient.

Also, {}.invoke() returns null, so I don't know where you got the impressions that even that returns a collection.

http://msdn.microsoft.com/en-us/library/system.management.automation.scriptblock.invokereturnasis(v=vs.85).aspx


Need Your Help

ffmpeg: How to add frames or black at the end of a video?

video ffmpeg frames

How to copy an arbitrary amount of duplicates of the last frame at the end of any video? Or alternatively, how to add black frames at the end of any video? Can this be done by scripting and ffmpeg?...