Using Powershell, System.Management.Automation.Cmdlet.Invoke() returns object of type '<Invoke>d__40'
rather than specified OutputType.
To reproduce:
powershell -NoProfile
Add-Type -Path .\ExampleCmdlet.cs
$command = [SendGreeting.SendGreetingCommand]::new()
$command.Name = 'Person'
$invoke = $command.Invoke()
$invoke.GetType()
Expected: [string]
Actual: [<Invoke>d__40]
$PSVersionTable:
Name Value
---- -----
PSVersion 5.1.19041.1237
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.19041.1237
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
Get-Member -InputObject $invoke
reveals this to implement IEnumerator and some playing with .MoveNext()
and .Current
will sometimes output the expected "Hello Person!"
result.
What is this <Invoke>d__40
type?
Why is $command.Invoke()
not returning the expected string output directly?
To complement Guru Stron's helpful answer, which explains that the name of the specific type returned is just an implementation detail; what matters is that the type implements the System.Collections.IEnumerable
interface:
The fact that the type also implements the System.Collections.IEnumerator
interface, as you've discovered, makes it a lazy (on-demand) enumerable: that is, the object returned doesn't itself contain data, it retrieves / generates data when enumerated.
If you output $invoke
, PowerShell implicitly enumerates the enumerable, and you should see the expected outcome:
PS> $invoke # enumeration happens here.
Hello Person!
Note that an attempt to access $invoke
again produces no output, because the enumeration has completed (and even trying to reset it with .Reset()
doesn't work, because the type implementing the interface doesn't support it).
.Reset()
method; e.g., in the following examples $enumerator
can be enumerated repeatedly, and yields the same results every time: $enumerator = [System.Linq.Enumerable]::Range(1,10)
and $enumerator = [System.IO.File]::ReadLines("$PWD/test.txt")
By contrast, assigning $invoke
to a variable does not cause enumeration: $result = $invoke
merely creates another reference to the enumerator itself.
In order to capture the actual object(s) to be enumerated, you must force enumeration via $()
, the subexpression operator or @()
, the array-subexpression operator; e.g.:
# Note: This assumes you haven't output $invoke by itself before.
$result = $($invoke) # force enumeration and store the enumerated object(s)
Taking a step back:
Lazy enumerables aren't that common in normal PowerShell code, and if you use them in an enumeration context - notably in the pipeline or in a foreach
statement - they'll work as expected.
When you assign a lazy enumerable to a variable, you need to be aware that you're storing just the enumerator, not the data it will enumerate.
If you use your sample cmdlet as it is meant to be used - by invoking it as command Send-Greeting
with a -Name
argument - the lazy enumerable is eliminated from the picture, because cmdlets output actual data:
# Directly outputs string 'Hello Person!'
Send-Greeting -Name Person
To make your sample cmdlet callable this way, you need to not only load the implementing type's assembly into your session with Add-Type
, you must additionally import it as a PowerShell module, with Import-Module
:
# Compile and load the assembly, and also import it as a PowerShell module,
# so the cmdlet that is implemented surfaces as such.
(Add-type -PassThru -LiteralPath .\ExampleCmdlet.cs).Assembly | Import-Module
# Now you can call your Send-Greeting cmdlet.
Send-Greeting -Name Person