powershelldecoratorcalculated-property

PSCustomObject with different string types


I have a workflow that is pretty picky with the PSCustomObjects it receives. I often use Select-Object to get the properties from the objects I want to keep and sometimes I need to convert some of the values to more usable formats. The easiest way to do this is to use the @{Name='Name'; Expression = {'Expression'}} technique.

BUT, that technique messes up the PSCustomObject in a way that blocks my further workflow.

The issue can be reproduced like this:

$object = [pscustomobject]@{
        Text        = "This is a string"
    }

In the output below, the Definition for string is 'string Text=This is a string'

$object | Select-Object * | Get-Member
<# Outputs
TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Text        NoteProperty string Text=This is a string
#>

When adding a new NoteProperty with the Select-Object technique, the Definition is 'System.String Text2=This is a string'

This is what makes my next cmdlet throw.

$WhatHappensToText = $object | Select-Object @{Name='Text'; Expression={$_.Text}} 
$WhatHappensToText | Get-Member
<# Outputs
TypeName: Selected.System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Text        NoteProperty System.String Text=This is a string
#>

When stripping the surplus like below, Definition is back to 'string Text2=This is a string'

Exporting to Clixml and re-importing does the same

$WhatHappensToText | ConvertTo-Json | ConvertFrom-Json | Get-Member
<# Outputs
TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Text        NoteProperty string Text=This is a string
#>

If I add the new NoteProperty like this, Definition is 'string Text2=This is a string' as I like it

$object2 = $object | Select-Object *
$object2 | foreach-object {$_ | Add-Member -MemberType NoteProperty -Name Text2 -Value $_.Text}
$object2 | Get-Member
<# Outputs
TypeName: Selected.System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Text        NoteProperty string Text=This is a string
Text2       NoteProperty string Text2=This is a string
#>

I have the following questions:

Why is the @{Name='Name'; Expression = {'Expression'}} technique adding System.String to the Definition and not string like in the Add-Member scenario?

Is there a way to make the @{Name='Name'; Expression = {'Expression'}} technique add a string and not a System.String?


Solution

  • Why is the @{Name='Name'; Expression = {'Expression'}} technique [i.e., using a calculated property] adding System.String to the Definition and not string like in the Add-Member scenario?

    This is a side effect of the unfortunate fact that the calculated-property technique creates a [psobject] wrapper around the property value specified.

    [psobject] is meant to be a transparent helper type used behind the scenes, and while such wrappers are typically invisible, they can situationally result in different behavior, such as in the case at hand.

    This problematic behavior, along with a list of scenarios where the behavior changes, is discussed in GitHub issue #5579.


    Is there a way to make the @{Name='Name'; Expression = {'Expression'}} technique add a string and not a System.String?

    This requires avoiding the [psobject] wrapper, which isn't possible with the calculated-property technique.

    Your options are: