Search code examples
arrayspowershellhashtabledsc

How do I update an array in an array in a hashtable in Powershell?


I'm using Powershell desired state configuration and have a separate definition document (psd1) I load into memory via

$ConfigData = Import-PowerShellDataFile "$Path" 

where I'd like to update the node names based on an environment variable. The overall psd1 file shows it's type as a hashtable (name and basetype are below), then allnodes shows up as an array, then nodename as another array.

Hashtable  System.Object                                                                                                 
Object[]   System.Array                                                                                                  
Object[]   System.Array   

Replace doesn't persist (like below). If I try to assign it back to itself or a copy, 'The property 'nodename' cannot be found on this object. Verify that the property exists yadda'

$ConfigData.AllNodes.NodeName -replace 'vms','vmp'
or
$ConfigDataHolder.AllNodes.NodeName = $ConfigData.AllNodes.NodeName -replace 'vms','vmp'

Direct reference/assigment doesn't persist, where below's output is still the servername previously, even in a clone scenario.

$ConfigData.AllNodes.NodeName[2] = "something"

Solution

    • Since you're using property access on arrays in order to access values of its elements, you're taking advantage of member-access enumeration.

    • However, member-access enumeration - by design - only works for getting values, not for setting (updating) them.

      • The behavior when updating is attempted is obscure, unfortunately, as you've experienced:

        • When you try to assign to a member-access-enumerated property as a whole, the error message is obscure: it tells you that no such property exists, because on setting it only looks at the array object itself, even though it does find it on getting, when it looks at the arrays elements; a minimal example:

           $data = @{ arr = @(@{ subarr = 1, 2 }, @{ subarr = 3, 4 }) } 
           # !! ERROR " property 'subarr' cannot be found on this object"
           $data.arr.subarr = @(42, 43) 
          
        • If you try a specific index (e.g. [2]), but the array that the index is applied to was itself obtained via member-access enumeration, the assignment in effect operates on a temporary array, and is therefore effectively discarded, quietly; a minimal example:

           $data = @{ arr = @(@{ subarr = 1, 2 }, @{ subarr = 3, 4 }) } 
           # !! IGNORED, because .arr.subarr is the result of
           # !! member-access enumeration.
           $data.arr.subarr[0] = 42 
          
    • The solution is to target the array elements individually for updating, either with a single index (e.g., [2]) or in a loop, one by one.

    A simplified example:

    $configData = @{
      AllNodes = @(
         @{
           NodeName = @(
            'Node1a', 
            'Node1b'
           ) 
         },
         @{ 
           NodeName = @(
             'Node2a',
             'Node2b'
           ) 
         }
      )
    }
    
    # OK: a specific element of *both* arrays involved - .AllNodes and .NodeName -
    #     is targeted and can therefore be assigned to.
    $configData.AllNodes[0].NodeName[1] = 'NewNode1b'