Jump to page sections

I discovered what seems like a simple and clever way of creating a deep copy of an array in PowerShell. It should definitely work on arrays containing so-called value types. I've only tested (successfully) with integers and strings (strings are reference types) in a generic array (System.Object[]).

PowerShell Objects (PSObjects) are apparently not deep copied this way, but I talk more about deep copying objects below. For creating a (sort of) deep copy of a PSObject, I found this on Stack Overflow, which uses a temporary hash, and calls the .Clone() method on it. As someone points out, the .Clone() method actually creates a shallow copy, but this can work in some cases. I mention a couple of tricks using ConvertTo-Csv, ConvertFrom-Csv, Export-CliXml, and Import-CliXml below.

The method I discovered for arrays is simply piping the current array through ForEach-Object, as I found myself able to demonstrate that this creates a deep copy of what's passed through (tested on System.Int32 and System.String).

In case you're wondering what a deep copy is: creating a deep copy means creating a separated copy of a value, for instance an array element. By default .NET and PowerShell use reference types. If you create an array $a and create a copy with $b = $a, when you change $a, the changes are also reflected in $b. A deep copy makes sure the new item is not connected to the old item.

This is likely more easily understood through an example.

# Create array with two elements.
PS C:\> $Array = @('a', 'b')

# Regular assignment (reference) copy.
PS C:\> $ArrayCopy = $Array 

# My array deep copy trick.
PS C:\> $ArrayDeepCopy = $Array | foreach { $_ } 

# Change the original array.
PS C:\> $Array[1] = 'xxx'

# Observe that the reference copy changes too.
PS C:\> $ArrayCopy[1] 
xxx

# But the deep copy did not change.
PS C:\> $ArrayDeepCopy[1] 
b

Be aware that this method will "flatten" arrays. To preserve nesting, use a comma as a unary operator (only one argument), in front of the current object. Study the following example for further enlightenment.

PS C:\> $Array = @( @(1..3), @(4..6) )

PS C:\> $ArrayCopy = $Array

PS C:\> $ArrayCopy.Length 2

PS C:\> $Array[0] = @(7..9)

PS C:\> $Array[0] 7 8 9 # This "regular" copy changed too. PS C:\> $ArrayCopy[0] 7 8 9

PS C:\> $ArrayDeepCopy = $Array | foreach { , $_ }

# Multi-dimensional array preserved. PS C:\> $ArrayDeepCopy.Length 2 # No comma. PS C:\> $ArrayDeepCopyFlat = $Array | foreach { $_ } # No comma means it's flattened into 6 elements. PS C:\> $ArrayDeepCopyFlat.Length 6

PS C:\> $Array[0] = @(1..3)

# No changes to the deep copy despite the line above. PS C:\> $ArrayDeepCopy[0] 7 8 9 PS C:\>

I also found this which mentions the array class' static method Copy().

This also works, as per the demonstration below, but it is a bit more work. You need to "prepare" a target array, that's of the same length as the array you're copying, and to specify the length when you call Copy().
PS C:\> $Array = @(1..3)

# Prepare target array.
PS C:\> $ArrayCopy = @(1..$Array.Count) | % { $_ * 2 } 

PS C:\> $ArrayCopy # different from $Array
2
4
6

PS C:\> [array]::Copy($Array, $ArrayCopy, $Array.Count)

PS C:\> $ArrayCopy # now the same as $Array 1 2 3

PS C:\> $Array[0] = 5

PS C:\> $Array[0] 5 # Unchanged, despite changing $Array. PS C:\> $ArrayCopy[0] 1

PSObjects

For PSObjects that don't have nested properties you can also serialize and get rid of the connection to the original object (the "$Object" variable in the example below), by converting to CSV and back via the PS cmdlets built into PSv2 and up: ConvertTo-Csv and ConvertFrom-Csv.
PS C:\temp> $Object = New-Object -TypeName PSObject -Property @{ key = 'value' }

# Create a regular (reference) copy.
PS C:\temp> $ObjectCopy = $Object

# Change original value.
PS C:\temp> $Object.key = 'ChangedValue'

# Observe that the copy changes too.
PS C:\temp> $ObjectCopy.Key
ChangedValue

PS C:\temp> $Object, $ObjectCopy | 
    foreach {$_.GetType().FullName}

System.Management.Automation.PSCustomObject
System.Management.Automation.PSCustomObject

PS C:\temp> $ObjectDeepCopy = $Object | 
    ConvertTo-Csv -NoTypeInformation | 
    ConvertFrom-Csv

PS C:\temp> $ObjectDeepCopy.GetType().FullName
System.Management.Automation.PSCustomObject

# Change original object.
PS C:\temp> $Object.key = "AnotherChange"

PS C:\temp> $ObjectCopy.key
AnotherChange

# Observe that deep copy does not change.
PS C:\temp> $ObjectDeepCopy.key
ChangedValue

For complex objects, you can use Export-CliXml and Import-CliXml, with a file as an intermediary step.

PS C:\temp> $Obj = [pscustomobject] @{ 
    key = 'array', 'of', 'values'
}

PS C:\temp> $Obj

key --- {array, of, values} PS C:\temp> $Obj | ConvertTo-Csv | ConvertFrom-Csv # Flawed/fails. key --- System.Object[] PS C:\temp> $Obj | Export-Clixml -LiteralPath .\serialized.xml

PS C:\temp> $ObjDeepCopy = Import-Clixml .\serialized.xml

PS C:\temp> $obj.key = 1..3 PS C:\temp> $obj.key 1 2 3

PS C:\temp> $ObjDeepCopy

key --- {array, of, values} PS C:\temp> $ObjDeepCopy.key array of values PS C:\temp> $ObjDeepCopy.key.Count 3

A serialization method is mentioned here, also on Stack Overflow. Currently (2016-10-09) the only answer there is that serialization. The foreach method and these other methods I described here are certainly a lot easier in some cases.

Side note and small rant: I tried answering the question above on the SO site, and spent 10 minutes writing a concise code example, but apparently I've been banned from answering. Likely because I posted 5-6 links to this site as answers without knowing it was considered bad form, in a short time span, and they were subsequently deleted by moderators, their algorithm started hating me, and now I'm stuck in limbo with only two possible (trivial and crap) answers for people to upvote, and I don't have enough reputation points to even comment (50 needed). That didn't work out so well. So, instead I wrote this article... Powershell      Windows      .NET      Programming          All Categories

Google custom search of this website only

Minimum cookies is the standard setting. This website uses Google Analytics and Google Ads, and these products may set cookies. By continuing to use this website, you accept this.

If you want to reward my efforts