10

I have a powershell script in which I do the following

$somePSObjectHashtables = New-Object Hashtable[] $somePSObject.Length;
$somePSObjects = Import-CSV $csvPath
0..($somePSObject.Length - 1) | ForEach-Object {
    $i = $_;
    $somePSObjectHashtables[$i] = @{};
    $somePSObject[$_].PSObject.Properties | ForEach-Object {
        $somePSObjectHashtables[$i][$_.Name] = $_.Value;
    }
}

I need to do this because I want to make several distinct copies of the data in the CSV to perform several distinct manipulations. In a sense I'm performing an "INNER JOIN" on the resulting array of PSObject. I can easily iterate through $somePSObjectHashtables with a ForEach-Object and call Hashtable.Clone() on each member of the array. I can then use New-Object PSObject -Property $someHashTable[$i] to get a deep copy of the PSObject.

My question is, is there some easier way of making the deep copy, without an intermediary Hashtable?

Justin Dearing
  • 14,270
  • 22
  • 88
  • 161
  • That's probably how I would have ended up solving such an issue. –  Feb 09 '12 at 03:07
  • Just to be sure about terms. `Hashtable.Clone()` makes shallow copies. Are we talking about deep copies or not? – Roman Kuzmin Feb 09 '12 at 06:55
  • 1
    Well, in this particular scenario, when data come from CSV, it will be *in fact* deep, because properties are either value types (deep copied, indeed) or strings (not deep copied but they are immutable, so we may consider them *deep copied* in that sense). – Roman Kuzmin Feb 09 '12 at 07:03
  • @RomanKuzmin You are right that Hashtable.Clone() does shallow copies. I had an issue with nested hashtables where that fact manifested itself to my annoyance. Thanks for clarifying. – Justin Dearing Feb 09 '12 at 21:07
  • Does binary serialization approach work in your scenarios? It worked for me in a few simple tests. – Roman Kuzmin Feb 10 '12 at 07:49
  • I didn't rewrite the code yet. I'll mark it as correct when I do. – Justin Dearing Feb 10 '12 at 14:47

3 Answers3

13

Note that here is a shorter, maybe a bit cleaner version of this (that I quite enjoy):

$data = Import-Csv .\test.csv

$serialData = [System.Management.Automation.PSSerializer]::Serialize($data)

$data2 = [System.Management.Automation.PSSerializer]::Deserialize($serialData)

Note: However, weirdly, it does not keep the ordering of ordered hashtables.

$data = [ordered] @{
    1 = 1
    2 = 2
}

$serialData = [System.Management.Automation.PSSerializer]::Serialize($data)

$data2 = [System.Management.Automation.PSSerializer]::Deserialize($serialData)
$data2

Will output:

Name                           Value
----                           -----
2                              2
1                              1

While with other types it works just fine:

$data = [PsCustomObject] @{
    1 = 1
    2 = 2
}

$data = @(1, 2, 3)
Cosmin
  • 131
  • 1
  • 7
7

For getting really deep copies we can use binary serialization (assuming that all data are serializable; this is definitely the case for data that come from CSV):

# Get original data
$data = Import-Csv ...

# Serialize and Deserialize data using BinaryFormatter
$ms = New-Object System.IO.MemoryStream
$bf = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
$bf.Serialize($ms, $data)
$ms.Position = 0
$data2 = $bf.Deserialize($ms)
$ms.Close()

# Use deep copied data
$data2
Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
  • 1
    Apologies for the late accept. I [tested it](https://gist.github.com/2006846) an it worked. One thing I notices id that you had to explicitly cast Get-Date calls as DateTime or else the serializer would complain thatit could not serialize PSObject. – Justin Dearing Mar 09 '12 at 14:56
  • 1
    @Peter McEvoy, FYI, I have created a PowerShell serializer: [ConvertTo-Expression](https://www.powershellgallery.com/packages/ConvertTo-Expression) – iRon Sep 07 '19 at 12:44
  • Is there an object "depth" limit to this approach? – Frank Lesniak Jan 21 '20 at 19:25
  • I am not aware of such a "depth". Maybe just the available memory is the limit. Also, cyclic references in the serialized graph, if any, should not be a problem, the binary serialization handles these. – Roman Kuzmin Jan 23 '20 at 10:59
4

Here's an even shorter one that I use as a function:

using namespace System.Management.Automation
function Clone-Object ($InputObject) {
    <#
    .SYNOPSIS
    Use the serializer to create an independent copy of an object, useful when using an object as a template
    #>
    [psserializer]::Deserialize(
        [psserializer]::Serialize(
            $InputObject
        )
    )
}
Justin Grote
  • 119
  • 2