3

Forgive the title, I'm not really sure how to explain what I'm seeing.

Sample Code:

    $SampleValues = 1..5
    $Result = "" | Select ID
    $Results = @()  

    $SampleValues | %{
        $Result.ID = $_
        $Results += $Result
    }

$Results

This is fairly straightforward:

  • Create an array with 5 numbers to be used in a loop
  • Create a temp variable with a NoteProperty called ID
  • Create an empty array to store results
  • Iterate through each of the 5 numbers assigning them to a temp variable then appending that to an array.

The expected result is 1,2,3,4,5 but when run this returns 5,5,5,5,5

This is a barebones example taken from a much more complex script and I'm trying to figure out why the result is what it is. In each iteration all elements that have already been added to $Results have their values updated to the most recent value. I've tested forcing everything to $Script: or $Global: scope and get the same results.

The only solution I've found is the following, which moves the $Result declaration into the loop.

    $SampleValues = 1..5
    $Results = @()

$SampleValues | %{
    $Result = "" | Select ID
    $Result.ID = $_
    $Results += $Result
    }

This works (you get 1,2,3,4,5 as your results). It looks like $Results is just holding multiple references to a singular $Result object but why does moving this into the loop fix the problem? In this example $Result is a string so perhaps it is creating a new object each iteration but even when I forced $Result to be an integer (which shouldn't recreate a new object since an integer isn't immutable like a string) it still fixed the problem and I got the result I expected.

If anybody has any insight into exactly why this fixes the problem I've be very curious. There are plenty of alternatives for me to implement but not understanding specifically why this works this way is bugging me.

2 Answers 2

9

It fixes the problem by moving into the loop because then you are then creating a new $Result object each time rather than changing a value on the same one (referenced 5 times in the array).

It doesn't have anything to do with whether you use "" | Select ID or 123 | Select ID because that just becomes a sort of property on the PSCustomObject, which is still a reference type rather than a value type.

Remember, Powershell is all .NET on the inside. Here is some C# that's analogous to what Powershell is doing in your first example that resulted in all 5s (hopefully you know C#):

var SampleValues = new []{1,2,3,4,5};
var Result = new CustomObject(){ ID = "" };
var Results = new List<Object>();

foreach (var _ in SampleValues) {
    Result.ID = _;
    Results.Add(Result);
}

Hopefully you can see how moving var Result = new CustomObject(){ ID = "" } inside the foreach loop would make it work better, and the same concept holds true in Powershell.

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

8 Comments

Joel, thank you for the comment. Integers are value types. I tried 'Result = 0 | Select ID' and both Result and ID were Int32. This is what confused me because I still got 5,5,5,5,5.
If you do $Result = 0 | Select ID, $Result.GetType() returns PSCustomObject with a basetype of System.Object. $Result.ID should be null initially. If you assign an integer ($Result.ID = 0), $Result.GetType() is still the same, but now $Result.ID.GetType() returns Int32, basetype System.ValueType.
Now I see why you thought $Result was of type Int32. And from searching, you're not the only one to find this behavior strange. However, it is indeed a reference type, no matter what Get-Member is saying.
Ah-HA! When $Result | Get-Member is displaying "TypeName: Selected.System.Int32", it is displaying the original type that was used in the Select-Object to create the PSCustomObject.
Here is some more explanation. PowerShell can add properties and members to objects using its Extended Type System. But you can't modify normal .NET objects like that. So PowerShell wraps them with a PSObject that does the magic. Sometimes you have this object directly (like after Select-Object). And sometimes it is transparent to you, like with these two examples. ps | select -first 1 | gm versus (ps | select -first 1).PSBase | gm
|
1

In this example $Result is a string so perhaps it is creating a new object each iteration but even when I forced $Result to be an integer (which shouldn't recreate a new object since an integer isn't immutable like a string) it still fixed the problem and I got the result I expected.

Actually, in your example $Result is not a string. It is a generic object with one propery (ID) that is a string.

Powershell variables come in 2 types - value and reference. A string or integer is passed by value, an object is passed by reference. By addind $result to $results 5 times you got 5 refereneces to the one $result object, not 5 different objects.

If we add the $result.id property (an integer / value type) to $results instead of the $result object we get:

 $SampleValues = 1..5
     $Result = "" | Select ID
     $Results = @()  

     $SampleValues | %{
         $Result.ID = $_
         $Results += $Result.ID
     }

 1
 2
 3
 4
 5

Comments

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.