Tag Archives: .psobject.copy

Why Did My PowerShell Object Change? The “Assignment by Reference” Trap

 “It ain’t what you don’t know that gets you into trouble. It’s what you know for sure that just ain’t so.“

Mark Twain

I just ran into this problem and beat my head against the keyboard, wall, and any other solid surface I could find for about an hour until I finally figured out the problem. Hopefully, this post will remind me how to fix this next time I run into this issue. Or perhaps it will help avoid this same issue in the future. Or (gasp!) you are currently facing this issue and need help to solve it.

Overview

  • I wanted to create a process where I get some value in a variable.
  • Along the way I make some changes to that variable.
  • At the end I want to compare the original value of that variable to the final value.

From a TSQL perspective, this is super easy! Just create a second variable and give that second variable the value of the original variable at the beginning before any changes are made to the original. Then at the end compare the values of the two variables to see what changed. Unfortunately, this is not done the same way in powershell when dealing with certain types of variables.

Demo time!

I created a variable and gave it some values:

# 1. Create our "original" object
$originalObject = [PSCustomObject]@{
    Name   = 'ServerA'
    Status = 'Online'
}
$originalObject | Format-List

Which gave the following results:

Name   : ServerA
Status : Online

Now I want to put the value of this “original” object into a new variable so I can have a copy of what the value was originally before I make some changes to the “original” object.

# 2. Create our "snapshot" by just assigning the variable
$snapshot = $originalObject

Next, let’s go ahead and edit the “original” object with a new value.

# 3. Now, let's modify the original object
$originalObject.Status = 'Offline'

Finally, let’s look at the value of both the “original” object and the snapshot object:

# 4. Finally, let's look at both variables
$originalObject | Format-List
$snapshot | Format-List

They are the same?!?!?! How can that be? I literally struggled for an hour trying to figure out what was wrong, thinking something in my code was changing the snapshot inadvertantly.

Name   : ServerA
Status : Offline

Name   : ServerA
Status : Offline

Why Did My Copied Object Change? (The Problem and the Fix)

In Powershell, when you have a custom powershell object and execute $variableA = $variableB, it is just a pointer/link to the same spot in RAM. You are not actually putting the data from $variableB into $variableA like you do in TSQL.

This kind of problem will not occur with all variable types in Powershell. For simple “value” variable types like $int, $string, and $boolean when you run $variableA = $variableB it will actually copy the data into the other variable. This is also known as a “shallow copy“.

However, for the more complex “reference” variable types like $array, $hashtable, and [pscustomobject] you will run into the issue described above with my example. This is because these types of variables just hold a pointer to a specific location in memory. When you run $variableA = $variableB, you are just “copying” that pointer within the variable, not the data itself.

Instead, you need to use the .psobject.COPY() method in PowerShell to copy that data from one custom object variable to the other. This is what is known as a “deep copy“.

Demo Part Deux (Correct solution)

To create a true, independent copy (a ‘deep copy’) of a [PSCustomObject], you must use the .psobject.copy() method.

Just like before I created a variable and gave it some values:

# 1. Create our "original" object
$originalObject = [PSCustomObject]@{
    Name   = 'ServerA'
    Status = 'Online'
}
$originalObject | Format-List

Which gave the following results:

Name   : ServerA
Status : Online

This time however, when I created the snapshot, I used the .psobject.COPY() psobject method in powershell to actually copy the data from one variable to the other.

# 2. Create our "snapshot" by just assigning the variable
$snapshot = $originalObject.psobject.copy()

Next, lets go ahead and edit the “original” object with a new value.

# 3. Now, let's modify the original object
$originalObject.Status = 'Offline'

Finally, lets take a look at the value of both the “original” object and the snapshot object:

# 4. Finally, let's look at both variables
$originalObject | Format-List
$snapshot | Format-List

This time I see the original value as well as the updated value! And the world rejoiced and there was much celebration.

Name   : ServerA
Status : Offline

Name   : ServerA
Status : Online

Conclusion

So the big take away here is not all programming languages are created equal. Go back to the basics if something isn’t working correctly as you expect. Most likely there is something simple and you don’t realize how something actually works.

As an aside, you can just use .copy alone without the .psobject part if you are just dealing with data tables from sql. Oddly enough I’ve been working in PowerShell now for 3 years at least and have never ran into this issue before. I guess I’ve been lucky!

Do you have any other “gotcha” moments you’ve run into when moving from TSQL to PowerShell? Share them below!


If you’d like to read more about this on the official MS Documentation pages:

PSObject.Copy Method: This is the official documentation for the exact method you’re using as the solution.

PowerShell Types (Value vs. Reference): This document is the best explanation of the why. It directly discusses “value types” and “reference types” and the difference between shallow and deep copies.

about_PSCustomObject: Since your example uses [PSCustomObject], this link is highly relevant for readers who want to know more about them.

about_Assignment_Operators: This explains what the simple equals sign (=) operator does, which is the heart of the “problem” part of your demo.

about_Objects: A good general-purpose link for readers who are new to the idea of objects in PowerShell.