I need some assistance please, to understand behavior of arrays and powershell loops. I have stumbled upon a strange array behavior, here is the issue and sample script:
# I create simple array
$item2 = New-Object Collections.ArrayList
# getting some sample data for array
$allitems = Get-ChildItem -Path "c:\" | select name,Attributes
#running loop
foreach ($item in $allitems){
# outputing iriginal array item
Write-Host $item
# addind to new array
$item2.Add($item)| Out-Null
# here is the issue I have - after I add member to my $item2 array
# it is replicated to also $allitems, I don't need this behavior
# to happen. Also I don't understand why it changes the original
# $item and $allitems. Am I creating array copy incorrectly ?
$item2[-1] | Add-Member -MemberType NoteProperty -name 'testproperty' -value 'testvalue'
write-host "$($item2[-1]) mod"
Write-Host $item
}
result of the script is this :
@{Name=rollback2.puc; Attributes=Archive} -> original entry
@{Name=rollback2.puc; Attributes=Archive; testproperty=testvalue} mod -> modified entry
@{Name=rollback2.puc; Attributes=Archive; testproperty=testvalue} -> original entry modified , why ?
Any help appreciated. Thanks
CodePudding user response:
Most types of objects in .NET are reference-type objects, meaning that what you have stored in $item
is not the object itself, but a reference to it.
When you then call $item2.Add($item)
, the reference is copied to $item2
, but both the array list and the variable still point back to the exact same object/memory address, and PowerShell will therefore "remember" the added property regardless of which copy of the object reference you use.
In this particular case, you can work around it and create 2 sets of objects by calling Get-ChildItem
twice:
$item2 = New-Object Collections.ArrayList
$allitems = Get-ChildItem -Path "c:\" | select name,Attributes
foreach ($item in Get-ChildItem -Path "c:\" | select name,Attributes){
Write-Host $item
$item2.Add($item)| Out-Null
$item2[-1] | Add-Member -MemberType NoteProperty -name 'testproperty' -value 'testvalue'
# the objects stored in $allitems will have been unaffected by the call above
}
Or by calling Get-Item
to create a new object to represent the file just before adding to the array list:
$item2 = New-Object Collections.ArrayList
$allitems = Get-ChildItem -Path "c:\" | select name,Attributes
#running loop
foreach ($item in $allitems){
Write-Host $item
# Create a new FileInfo object by calling `Get-Item`
$itemCopy = $item | Get-Item
$item2.Add($itemCopy) | Out-Null
$item2[-1] | Add-Member -MemberType NoteProperty -name 'testproperty' -value 'testvalue'
# $item2[-1] has been modified, but $item has not
}