PSCustomObject mystery

This topic contains 7 replies, has 6 voices, and was last updated by Profile photo of Dudebro Dudebro 2 months, 3 weeks ago.

  • Author
    Posts
  • #67951
    Profile photo of Dudebro
    Dudebro
    Participant

    I'm writing a function that returns several PSCustomObjects, but the results are unexpected (to me). Below is a mock function that exhibits the same behavior.

    Function New-CoolObject
    {
        $CoolObject     = [PSCustomObject] @{
            Name        = "CoolObject"
            CoolString  = $Null
        }
    
        $CoolObject.CoolString = "Cool"
        $CoolObject
    
        $CoolObject.CoolString = "Uncool"
        $CoolObject
    }
    

    What I want to do is to return two separate objects that are similar, but with some different values. The real function returns a large amount of properties that I was hoping I wouldn't have to define in multiple places. Running the function seems to do what I want:

    PS C:\Cool> New-CoolObject
    
    Name       CoolString
    ----       ----------
    CoolObject Cool
    CoolObject Uncool
    

    However, when using the same function to populate a variable, the results are different:

    PS C:\Cool> $Result = New-CoolObject
    PS C:\Cool> $Result
    
    Name       CoolString
    ----       ----------
    CoolObject Uncool
    CoolObject Uncool
    

    I get the feeling that I'm actually not returning two separate objects, but I don't understand why. Can anyone explain?

  • #67956
    Profile photo of JC Ruiz
    JC Ruiz
    Participant

    Very interesting. I've been able to repro it.
    Which PowerShell version are you using ? 5.1.14393.953 in my case.

    My investigation,

    1. Changing the assignment (using Set-Variable instead of X=), and inserting a quick-and-dirty Write-Host in the pipeline,
    shows the objects are created and passed properly, but the assignment gets wrong results :
    New-CoolObject | % {Write-host $_; $_} | set-variable X

    2. Adding a Trace-Command might provide some extra info : (I'll check later, but let's try in parallel)
    trace-command { New-CoolObject | % {Write-host $_; $_} | set-variable X } -pshost -name *

  • #67971
    Profile photo of Dudebro
    Dudebro
    Participant

    Hello, and thank you for the reply!

    I'm using the same version of PowerShell: 5.1.14393.953

    I'm not experienced with interpreting the results from Trace-Command, but it looks to me like Set-Variable is at least getting the right values:

    ...
    DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Set-Variable]
    DEBUG: ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.Management.Automation.PSCustomObject]
    DEBUG: ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
    DEBUG: ParameterBinding Information: 0 :     Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
    DEBUG: MemberResolution Information: 0 :     Enumeration Start
    DEBUG: MemberResolution Information: 0 :         Generating the total list of members
    DEBUG: MemberResolution Information: 0 :             Type table members: 0.
    DEBUG: MemberResolution Information: 0 :             Adapted members: 0.
    DEBUG: MemberResolution Information: 0 :         Enumerating PSObject with type "System.Management.Automation.PSCustomObject".
    DEBUG: MemberResolution Information: 0 :         PSObject instance members: 2
    DEBUG: ParameterBinderController Information: 0 :  WriteLine       Adding PipelineParameter name=Value; value=@{Name=CoolObject; CoolString=Cool}
    ...
    DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Set-Variable]
    DEBUG: ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.Management.Automation.PSCustomObject]
    DEBUG: ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
    DEBUG: ParameterBinding Information: 0 :     Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
    DEBUG: MemberResolution Information: 0 :     Enumeration Start
    DEBUG: MemberResolution Information: 0 :         Generating the total list of members
    DEBUG: MemberResolution Information: 0 :             Type table members: 0.
    DEBUG: MemberResolution Information: 0 :             Adapted members: 0.
    DEBUG: MemberResolution Information: 0 :         Enumerating PSObject with type "System.Management.Automation.PSCustomObject".
    DEBUG: MemberResolution Information: 0 :         PSObject instance members: 2
    DEBUG: ParameterBinderController Information: 0 :  WriteLine       Adding PipelineParameter name=Value; value=@{Name=CoolObject; CoolString=Uncool}
    ...
    
  • #67972
    Profile photo of Sam Boutros
    Sam Boutros
    Participant

    Try this:

    Function New-CoolObject {
    
        $CoolObject     = [PSCustomObject] @{
            Name        = 'CoolObject'
            CoolString  = $Null
        }
    
        $CoolObject.CoolString = 'Cool'
        $CoolObject
    
        Remove-Variable CoolObject
        $CoolObject.CoolString = 'Uncool'
        $CoolObject
    }
    

    The second assignment $CoolObject.CoolString = 'Uncool' reaches back and changes the previous assigned value since you're manipulating the same $CoolObject. To fix this and prevent the reach back, get a new $CoolObject by adding Remove-Variable CoolObject before every $CoolObject assignment..

  • #67975
    Profile photo of Fred Fernandes
    Fred Fernandes
    Participant

    I could be wrong, but when you call the function on it's own, it will display the result of CoolString each time it's referenced but when you call it to be sent to a variable, only the last call of CoolString gets stored. I may not be explaining that correctly.

  • #67977
    Profile photo of Max Kozlov
    Max Kozlov
    Participant

    that happen because you have only one object
    when your function return $CoolObject it return the same object twice because object is reference type
    you can try this:

     C:\> $Result = New-CoolObject
     C:\> $Result
    
    Name       CoolString
    ----       ----------
    CoolObject Uncool
    CoolObject Uncool
    
     C:\> $Result[0].CoolString='Very Cool'
     C:\> $Result
    
    Name       CoolString
    ----       ----------
    CoolObject Very Cool
    CoolObject Very Cool
    

    Invoking New-CoolObject without assigning to variable in fact just display object current state on the screen (host)
    and you see two different view of the same object because of "snapshot" effect
    If you want to get two different objects you can clone first object

    Function New-CoolObject
    {
        $CoolObject     = [PSCustomObject] @{
            Name        = "CoolObject"
            CoolString  = $Null
        }
    
        $CoolObject.CoolString = "Cool"
        $CoolObject
    
        # clone can be saved into the same or different variable. I use the same
        $CoolObject = $CoolObject.PSObject.Copy()
    
        $CoolObject.CoolString = "Uncool"
        $CoolObject
    }
    $Result = New-CoolObject
    $Result
    
    Name       CoolString
    ----       ----------
    CoolObject Cool
    CoolObject Uncool
     C:\>
    
  • #67987
    Profile photo of Dan Potter
    Dan Potter
    Participant

    No mystery.. you are creating one object.

    
    Function New-CoolObjects{
    
     param($objects)
    
    foreach($i in $objects){
    
        [PScustomobject]@{
            Name = "CoolObject"
            CoolString = $i
        }
    
    
    }
        
    
    }
    
    
    new-coolobjects 1,2
    
    
  • #68043
    Profile photo of Dudebro
    Dudebro
    Participant

    Thanks everyone! I guess the mystery is solved. 😉

    Looks like the PSObject.Copy() method is the way to go in my case.

You must be logged in to reply to this topic.