Powershell: Write-Output -NoEnumerate wrap singular items into PSObject[]

Created on 15 Oct 2017  Â·  15Comments  Â·  Source: PowerShell/PowerShell

As far as I understand, issue caused by #2038.

Steps to reproduce

(Write-Output -NoEnumerate 1).GetType().FullName
(1 | Write-Output -NoEnumerate).GetType().FullName

Expected behavior

System.Int32
System.Int32

Actual behavior

System.Management.Automation.PSObject[]
System.Management.Automation.PSObject[]

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.0.0-beta.8
PSEdition                      Core
GitCommitId                    v6.0.0-beta.8-46-ge3397b63e756bf432bbe80f5e9c4407d52d6b5b9
OS                             Microsoft Windows 10.0.15063
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Area-Cmdlets-Utility Issue-Bug

Most helpful comment

Minimal repros on PSCore 6.2.1:

PS C:\WINDOWS\system32> (Write-Output -NoEnumerate @()).GetType().FullName
System.Object[]
PS C:\WINDOWS\system32> $object = [pscustomobject]@{Name = 'Test'}
PS C:\WINDOWS\system32> (Write-Output $object -NoEnumerate).GetType().FullName
System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

All 15 comments

Whoops.. meant to cancel my comment, not close the issue. I'm not sure if this is a bug or by design, but I'll label it as a bug until someone says otherwise.

Hi @PetSerAl PetSerAl,

Actually this is by design from all the way back in v1.0. For consistency reasons, the team at that time decided that cmdlets would always return PSObjects. The PowerShell engine invocation APIs also always return collections of PSObject.

@BrucePay As far as I understand PSObject and PSObject[] are different things. And that change in behavior of Write-Object was introduced quite recently. In v1.0 – v5.1 Write-Object does not behave that way.

@vexx32 Write-Output -NoEnumerate is not _fully_ fixed in PS6.2.0 and up:

See: https://github.com/PowerShell/PowerShell/issues/5122

PS C:\Users\john.zabroski> ($PSVersionTable).PSVersion

Major  Minor  Patch  PreReleaseLabel BuildLabel
-----  -----  -----  --------------- ----------
6      2      1

PS C:\Users\john.zabroski> (Write-Output -NoEnumerate 1).GetType().FullName
System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

PS C:\Users\john.zabroski> (1 | Write-Output -NoEnumerate).GetType().FullName
System.Int32

and also (I would argue the following is correct behavior, but just want to document it so that any fix to the above doesn't break this scenario):

PS C:\Users\john.zabroski> (Write-Output -NoEnumerate @()).GetType().FullName
System.Object[]

The problem still exists for:

  • Direct argument
  • Not a collection

The problem is fixed for:

  • Pipeline argument
  • Collection or scalar value

🤔 very curious. Okay. Will have to dig into that some...

I just ran into a scenario with this that gives an error on PowerShell 7 preview 5 but works fine on Windows PowerShell.

$object = [pscustomobject]@{Name = 'Test'}
$result = (Write-Output $object -NoEnumerate)
$result.Name = 'New Name'

Because $result is wrapped, I get this error:

InvalidOperation:
Line |
   3 | $result.Name = 'New Name'
     | ^ The property 'Name' cannot be found on this object. Verify that the property exists and can be set.

I ran into this trying to get an existing module to work on PowerShell 7. So this is a simplified example of something I found in the wild.

Interesting. Looks like we still have some adjustments to make to the binder. Not sure where those changes would need to be made, but that scenario seems pretty clear we want to have that work. @daxian-dbw any thoughts?

Minimal repros on PSCore 6.2.1:

PS C:\WINDOWS\system32> (Write-Output -NoEnumerate @()).GetType().FullName
System.Object[]
PS C:\WINDOWS\system32> $object = [pscustomobject]@{Name = 'Test'}
PS C:\WINDOWS\system32> (Write-Output $object -NoEnumerate).GetType().FullName
System.Collections.Generic.List`1[[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

Confirmed that is the same on 7-preview.5.

I _think_ we may be able to fix this scenario by altering the binder to use List[PSObject] rather than List[object] -- by using List[object] here in binding the ValueFromRemainingArguments parameter, it looks like Write-Output is being forced to discard all ETS / instance members on any input objects. That doesn't strike me as a good behaviour at all.

We should probably try to fix this for PS7 if we can, @SteveL-MSFT?

@vexx32 we can still take fixes if the fix itself isn't too risky. Are you going to submit a PR?

I don't know how risky said fix is, or if it's appropriate, and I'm not familiar with the code for the parameter binder itself.

That said, I'm sure I can follow the breadcrumbs and figure it out. I'd prefer if we can get @daxian-dbw's thoughts first, as I think he's pretty familiar with the parameter binder.

@vexx32 we can still take fixes if the fix itself isn't too risky. Are you going to submit a PR?

Changes in the parameter binder is always risky, especially we are nearly at the end of 7.0.0 cycle 😄
That being said, it's definitely worth to better understand the cause of the problem first.

@daxian-dbw are there unit tests for this I can look at?

I might just try taking a stab at fixing it and seeing if my assumption is correct or causes some other problem I'm not expecting. Are you familiar with where this handling for ValueFromRemainingArguments takes place @daxian-dbw? It might be worth taking a look.

I'd rather not wait till post-ps7 to fix Write-Output 😕 -- doing so likely guarantees folx recommend heavily against using it and/or even using ValueFromRemainingArguments to avoid these unpredictable behaviours.

As for a _workaround_:

Use -InputObject explicitly rather than positional parameter binding:

# OK in v6.2+, thanks to -InputObject
(Write-Output -NoEnumerate -InputObject 1).GetType().Name | Should -Be Int32

This is a somewhat ironic reversal from Windows PowerShell, where the inverse workaround (_omitting_ -InputObject) is required to make -NoEnumerate behave properly (with input _collections_).

Was this page helpful?
0 / 5 - 0 ratings