1

I was working on something today and during testing I noticed a very peculiar issue

$arry = @()
$Msg = @{Body="This is a Sample Message";}

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg1"

$arry += $Msg
$arry | ConvertTo-Json    # 1st Result

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg2"

$arry += $Msg

$arry | ConvertTo-Json

The 1st result of $arry | ConvertTo-Json is as below

{ "Body": "This is a Sample Message", "BrokerProperties": { "Label": "Msg1" } }

The 2nd result of $arry | ConvertTo-Json is as below

[ { "Body": "This is a Sample Message", "BrokerProperties": { "Label": "Msg2" } }, { "Body": "This is a Sample Message", "BrokerProperties": { "Label": "Msg2" } } ]

What I thought would happen is when I set $Msg.BrokerProperties.Label= "Msg2" for 2nd time , then it would only effect the 2nd hashtable in array. but very interestingly that property is getting injected even on to 1st hashtable.

Can someone please explain this behaviour?

I was actually doing this in a loop for prepare a JSON payload for sending on to API call,so I am lookign for a way to update labels for each inner within the json object

Pavan Keerthi
  • 1,471
  • 3
  • 17
  • 22
  • Does changing the array append operation to `$arry += ,$Msg` change the behaviour? – arco444 Jun 04 '15 at 13:29
  • No,I am now starting to realise Arrays are Reference types (not value types) – Pavan Keerthi Jun 04 '15 at 13:34
  • When I try the below line by line the Label property is already in $msg when I output it $arry = @() $Msg = @{Body="This is a Sample Message";BrokerProperties=@{Priority="Medium"}} $Msg1 = $Msg $Prp = $Msg1.Get_Item("BrokerProperties") $Prp.Add("Label","Msg1") $arry += $Msg1 $Msg2 = $Msg $Msg2 | ConvertTo-Json – Pavan Keerthi Jun 04 '15 at 13:37

2 Answers2

3

Apparently a Powershell array holds only object pointers if a variable is added to it, so you have just added two references to a single object into your arry. To clarify, after your operations this statement:

$arry[0] -eq $arry[1]

will return true. To remedy, you should use clone() function to create an entirely new and independent object from $Msg, so that any modifications will not alter the object you've stored in your array.

$arry = @()
$Msg = @{Body="This is a Sample Message";}

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg1"

$arry += $Msg
$arry | ConvertTo-Json    # 1st Result

$Msg=$Msg.clone() # create a new copy
$Msg.BrokerProperties.Label= "Msg2" # alter new copy

$arry += $Msg

$arry | ConvertTo-Json # get two different tables in array

EDIT: In your case, you have to clone BrokerProperties as well, because it is also a hashtable, and cloning an upper level $Msg results in two different objects which contain links to a single nested hashtable. So, in order to receive a completely different object, you have to do a deep copy of your hash table.

$arry = @()
$Msg = @{Body="This is a Sample Message";}

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg1"

$arry += $Msg
$arry | ConvertTo-Json    # 1st Result

# $Msg=$Msg.clone() this is not enough!!!
$memStream = new-object IO.MemoryStream
$formatter = new-object Runtime.Serialization.Formatters.Binary.BinaryFormatter
$formatter.Serialize($memStream,$Msg) # serialization makes a string out of an object's structure
$memStream.Position=0
$Msg = $formatter.Deserialize($memStream) # deserialization makes a completely different object
$Msg.BrokerProperties.Label= "Msg2" # and now changing the nested hash table won't change the old one.

$arry += $Msg

$arry | ConvertTo-Json # get two different tables in array

On a side note: If you are creating multiple objects based on the same "template" object, you don't need to serialize all the time, just keep the references to $memStream and $formatter (one memory stream per object to deep copy, one formatter per script) and just call $memstream.position=0; $formatter.deserialize($memstream) to receive another prepared copy of the same object previously serialized.

Community
  • 1
  • 1
Vesper
  • 18,599
  • 6
  • 39
  • 61
2

Hashtables are passed by reference rather than by value.

To create a new hashtable from an existing hashtable, use Clone():

$arry += [hashtable]$Msg.Clone()

Be warned that this creates a shallow clone, so if you have nested hashtables, the inner most entries will still be reference types, and depending on the circumstances, you'd might want to write you own cloning function

Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206