2

My goal is to create a custom data object that has two discrete variables (fooName and fooUrl) and a list of fooChildren, each list item having two discrete variables variables childAge and childName.

Currently, I have this:

$fooCollection = [PSCustomObject] @{fooName=""; fooUrl=""; fooChildrenList=@()} 

$fooCollection.fooName = "foo-a-rama"
$fooCollection.fooUrl = "https://1.2.3.4"

$fooChild = New-Object -TypeName PSobject
$fooChild | Add-Member -Name childAge -MemberType NoteProperty -Value 6
$fooChild | Add-Member -Name childName -MemberType NoteProperty -Value "Betsy"
$fooCollection.fooChildrenList += $fooChild

$fooChild = New-Object -TypeName PSobject
$fooChild | Add-Member -Name childAge -MemberType NoteProperty -Value 10
$fooChild | Add-Member -Name childName -MemberType NoteProperty -Value "Rolf"
$fooCollection.fooChildrenList += $fooChild

cls
$fooCollection.fooName
$fooCollection.fooUrl

foreach ($fooChild in $fooCollection.fooChildrenList)
{
    ("  " + $fooChild.childName + " " + $fooChild.childAge)
}

Which produces the following. So far so good

foo-a-rama
https://1.2.3.4
  Betsy 6
  Rolf 10

Problem: I don't like using += because as I understand it, using += results in a copy of $fooCollection.fooChildrenList being created (in whatever state it's in) each time += is executed.

So, instead of implementing fooChildrenList as @(), I want to implement fooChildrenList as New-Object System.Collections.ArrayList so I can add each row as needed. I've tried various ways of doing this in code but fooChildrenList winds up being unpopulated. For example:

$fooCollection = [PSCustomObject] @{fooName=""; fooUrl=""; fooChildrenList = New-Object System.Collections.ArrayList} 

$fooCollection.fooName = "foo-a-rama"
$fooCollection.fooUrl = "https://1.2.3.4"

$fooChild.childName = "Betsy"
$fooChild.childAge = 6
$fooCollection.fooChildrenList.Add((New-Object PSObject -Property $fooChild))

$fooChild.childName = "Rolf"
$fooChild.childAge = 10
$fooCollection.fooChildrenList.Add((New-Object PSObject -Property $fooChild))

$fooCollection | get-member shows

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()                            
fooChildrenList NoteProperty System.Collections.ArrayList fooChildrenList=
fooName         NoteProperty string fooName=foo-a-rama                    
fooUrl          NoteProperty string fooUrl=https://1.2.3.4  

$fooCollection shows

fooName         : foo-a-rama
fooUrl          : https://1.2.3.4
fooChildrenList : {} 

How do I add a System.Collections.ArrayList to a PowerShell custom object?

3 Answers 3

3

The challenge is to add a copy of the $fooChild [pscustomobject] instance you're re-using every time you add to the list with .Add() (if you don't use a copy, you'll end up with all list elements pointing to the same object).

However, you cannot clone an existing [pscustomobject] (a.k.a [psobject]) instance with New-Object PSObject -Property.

One option (PSv3+) is to define the reusable $fooChild as an ordered hashtable instead, and then use a [pscustomobject] cast, which implicitly creates a new object every time:

$fooCollection = [PSCustomObject] @{ fooChildrenList = New-Object Collections.ArrayList } 

# Create the reusable $fooChild as an *ordered hashtable* (PSv3+)
$fooChild = [ordered] @{ childName = ''; childAge = -1 }

# Create 1st child and add to list with [pscustomobject] cast
$fooChild.childName = 'Betsy'; $fooChild.childAge = 6
$null = $fooCollection.fooChildrenList.Add([pscustomobject] $fooChild)

# Create and add another child.
$fooChild.childName = 'Rolf'; $fooChild.childAge = 10
$null = $fooCollection.fooChildrenList.Add([pscustomobject] $fooChild)

# Output the children
$fooCollection.fooChildrenList

Note the $null = ..., which suppresses the typically unwanted output from the .Add() method call.

The above yields:

childName childAge
--------- --------
Betsy            6
Rolf            10

A slightly more obscure alternative is to stick with $fooChild as a [pscustomobject] instance and call .psobject.Copy() on it to create a clone.


ArcSet's helpful answer provides a more modular solution that creates new custom-object instances on demand via a helper function.


Finally, in PSv5+ you could define a helper class:

$fooCollection = [PSCustomObject] @{ fooChildrenList = New-Object Collections.ArrayList } 

# Define helper class
class FooChild {
  [string] $childName
  [int]    $childAge
}

# Create 1st child and add to list with [pscustomobject] cast
$null = $fooCollection.fooChildrenList.Add([FooChild] @{ childName = 'Betsy'; childAge = 6 })

# Create and add another child.
$null = $fooCollection.fooChildrenList.Add([FooChild] @{ childName = 'Rolf'; childAge = 10 })

# Output the children
$fooCollection.fooChildrenList

Note how instances of [FooChild] can be created by simply casting a hashtable that has entries matching the class property names.

Sign up to request clarification or add additional context in comments.

Comments

2

Well im not sure what issue you are getting it works fine for me

function New-Child([string]$Name, [int]$Age){
    $Child = New-Object -TypeName PSobject
    $Child | Add-Member -Name childAge -MemberType NoteProperty -Value $age -PassThru | 
    Add-Member -Name childName -MemberType NoteProperty -Value $name
    return $child
}

$fooCollection = [PSCustomObject] @{fooName=""; fooUrl=""; fooChildrenList = New-Object System.Collections.ArrayList} 

$fooCollection.fooName = "foo-a-rama"
$fooCollection.fooUrl = "https://1.2.3.4"
$fooCollection.fooChildrenList.Add((New-Child -Name "Betty" -Age 9)) | Out-Null
$fooCollection.fooChildrenList.Add((New-Child -Name "Ralf" -Age 15)) | Out-Null

$fooCollection.fooName
$fooCollection.fooUrl
foreach ($fooChild in $fooCollection.fooChildrenList)
{
    "  " + $fooChild.childName + " " + $fooChild.childAge
}

output

foo-a-rama
https://1.2.3.4
  Betty 9
  Ralf 15

3 Comments

I was in the same ballpark as you, but I have to assume the way I was trying to create the new 'child' object was the problem (see my code under the heading Problem in my original post above). In any case, your solution works. My answer below seems to mimic yours kind of obliquely :-)
@NovaSysEng: The immediate problem with your code - as posted - is that you can't pass an existing [pscustomobject] / [psobject] instance to New-Object PSObject -Property so as to create a copy of it; the potential conceptual problem is that if you do not create a copy, you'll end up with a list whose elements all point to the very same object. ArcSet's answer avoids this problem by ensuring that a new custom-object instance is returned whenever function New-Child is called. Given your own answer, it sounds like adding the same object every time was indeed your true problem.
I would love to say my answer is right...and though my answer is correct. @Mklement0 answer is far more understandable and delivers a far more useful information then mine :) but thank your for accepting mine. But you might want to accept Mklement0's
0

Quick copy paste from something I have that I use to make some of my Arrays. I have to create the custom objects and then add them to the Array. It will need modified for your scenario but I think it will get you what you need.

[System.Collections.ArrayList]$SQL_Query_Results = @()

ForEach ($SQL_Index in $SQL_Table) {

    $SQL_Array_Object = [PSCustomObject]@{
        'Computer_Name'    = $SQL_Table[$SQL_Index_Counter].ComputerID -replace ",", ""
        'Project'          = $SQL_Table[$SQL_Index_Counter].Project
        'Site'             = $SQL_Table[$SQL_Index_Counter].Site
        'Description'      = $SQL_Table[$SQL_Index_Counter].Description -replace ",", ""
        'Physical_Machine' = $SQL_Table[$SQL_Index_Counter].IsPhysicalMachine
        'Active'           = $SQL_Table[$SQL_Index_Counter].IsActive
    }

    $SQL_Query_Results.Add($SQL_Array_Object) | Out-Null
}

Edited to show how Array was initially created.

5 Comments

I assume $SQL_Query_Results was defined as $SQL_Query_Results = New-Object System.Collections.ArrayList ?
[System.Collections.ArrayList]$SQL_Query_Results = @() is how I did the initial creation
what's the type for $SQL_Table and where does $SQL_Index_Counter come from (as in $SQL_Table[$SQL_Index_Counter].ComputerID -replace ",", "")? (I don't see $SQL_Index_Counter initialized or incremented)
For that specific scenario, it was all String but i have used Int, bool, datetime, etc as well. It will be what you have it defined as when you store it in the PSCustomObject
Sorry, also, I just realized - I'm trying to implement a System.Collections.ArrayList inside a PSCustomObject (or otherwise avoid using +=). This example doesn't crack that nut

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.