I\'m making a PowerShell script and came across a weird problem (for my world view at least :)) Here\'s the object $Source with 1 property and some integer values:
You're right about the $ChangedSource
being nothing more than a reference to the $Source
object.
For what you want, you could simply make a copy of the $Source
object by doing
$ChangedSource = $Source | Select-Object *
Example:
$Source= [PsCustomObject]@{'Priority' = 43.37},
[PsCustomObject]@{'Priority' = 26.51},
[PsCustomObject]@{'Priority' = 23.69},
[PsCustomObject]@{'Priority' = 6.43}
$ChangedSource = $Source | Select-Object *
$ChangedSource | ForEach-Object {$_.Priority = 100}
Write-Host '$Source' -ForegroundColor Yellow
$Source | Format-Table
Write-Host '$ChangedSource' -ForegroundColor Yellow
$ChangedSource | Format-Table
Output:
$Source
Priority
--------
43.37
26.51
23.69
6.43
$ChangedSource
Priority
--------
100
100
100
100
This works, because the Priority values are just numbers.
However, if the $source
object contains other objects, and you want to clone this into another object, you will still end up with references to the same objects inside the source and the copy.
If you want to be able to manipulate the copy while keeping the source intact, you'll need to 'Deep-Clone' the source object.
For that you can use below function:
function Clone-Object ([object]$obj, [switch]$DeepClone) {
if ($DeepClone) {
# create a deep-clone of an object
$ms = New-Object System.IO.MemoryStream
$bf = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
$bf.Serialize($ms, $obj)
$ms.Position = 0
$clone = $bf.Deserialize($ms)
$ms.Close()
}
else {
# create a shallow copy of same type
$clone = New-Object -TypeName $($obj.GetType().FullName)
foreach ($pair in $obj.GetEnumerator()) { $clone[$pair.Key] = $pair.Value }
}
return $clone
}
As mklement0 commented, the above function has limitations.
Here a new version that (hopefully) does a better job.
With the -DeepClone
switch, the function now tries to clone the object using [System.Management.Automation.PSSerializer]::Serialize()
if the source objects type attributes does not have the 'Serializable' flag set.
If that too fails, an error will be written out.
Without the -DeepClone
switch, a test is done first to make sure the source object implements the IEnumerable Interface. If that is the case, it tries to create a shallow clone to return an object of the same type.
Otherwise, a copy of the object is made using $clone = $obj | Select-Object *
which has the properties from the source object, but will be of a different type.
Otherwise it tries to create a shallow clone to return an object of the same type.
Please feel free to improve it.
function Clone-Object ([object]$obj, [switch]$DeepClone) {
if ($DeepClone) {
# create a deep-clone of an object
# test if the object implements the IsSerializable Interface
if ([bool]($obj.GetType().IsSerializable)) { # or: if ([bool]($obj.GetType().Attributes -band 'Serializable')) {
$ms = New-Object System.IO.MemoryStream
$bf = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
$bf.Serialize($ms, $obj)
$ms.Position = 0
$clone = $bf.Deserialize($ms)
$ms.Close()
}
else {
# try PSSerializer that serializes to CliXml
# source: https://stackoverflow.com/a/32854619/9898643
try {
$clixml = [System.Management.Automation.PSSerializer]::Serialize($obj, 100)
$clone = [System.Management.Automation.PSSerializer]::Deserialize($clixml)
}
catch {
Write-Error "Could not Deep-Clone object of type $($obj.GetType().FullName)"
}
}
}
else {
# create a shallow copy of the same type
# if the object has a Clone() method
if ($obj -is [System.ICloneable]) {
$clone = $obj.Clone()
}
# test if the object implements the IEnumerable Interface
elseif ($obj -is [System.Collections.IEnumerable]) {
try {
$clone = New-Object -TypeName $($obj.GetType().FullName) -ErrorAction Stop
foreach ($pair in $obj.GetEnumerator()) { $clone[$pair.Key] = $pair.Value }
}
catch {
Write-Error "Could not Clone object of type $($obj.GetType().FullName)"
}
}
else {
# this returns an object with the properties copied,
# but it is NOT OF THE SAME TYPE as the source object
$clone = $obj | Select-Object *
}
}
return $clone
}