1

I'm trying to add an array of hashtables to an existing array of hastables in Powershell, adding a single item works fine but I can't seem to find out how to add multiple hashtables to an existing array.

For example I have the below;

$ToAdd = @()
$ToAdd += (@{ 
        id   = "1234";
        type = "Scope";
    })

I then want to be able to add the $ToAdd array of hashtables to the object below ($helloBody.helloAccess.AccessRequired)

  $HelloBody = [ordered] @{
    "helloAccess" = @(
        @{
            hello = "hello"
            accessRequired = @(
                @{
                    id   = "random guid" 
                    type = "item 123"   
                }
                
                
            )
                              
        }
    )
    
} 

How is it possible to add an array of hashtables into another array?

When I try $helloBody.helloAccess.AccessRequired += $ToAdd, I get the following error:

A hash table can only be added to another hash table.
5

1 Answer 1

3

In order to update the .accessRequired entry of your nested hashtable, you must avoid member-access enumeration:

In your case, this means that you must access the first element of the array stored in .helloAccess explicitly:

$helloBody.helloAccess[0].AccessRequired += $ToAdd

Omitting [0] doesn't work, because member-access enumeration (regrettably) unwraps a single-element array instead of reporting it as such.

In concrete terms:

  • $helloBody.helloAccess performs member-access enumeration on the single-element array stored in that property, and returns the AccessRequired entry from each element.

  • The one and only .AccessRequired entry is itself a single-element array, and member-access enumeration unwraps that single-element array, yielding its only element directly, which is a hashtable.

  • += / + on a hashtable only works if the operand is also a hashtable, in which case the entries are merged; using any other type as the operand causes the error you saw (the error message is a bit confusing, but that is what it is trying to tell you).

Note:

  • Member-access enumeration unwraps single-element arrays, because it emulates the behavior of the ForEach-Object cmdlet in the pipeline, which, as cmdlets generally do, emits objects to the pipeline one by one by enumerating them, so that Write-Output -NoEnumerate @('foo') | ForEach-Object { $_ } yields the same as 'foo' | ForEach-Object { $_ }: A single-element array is treated the same as its one and only element, and the array wrapper is lost.

    • For reasons of backward compatibility this behavior is unlikely to change; see GitHub issue #6802 for a discussion.
  • More generally, note that you can only use member-access enumeration for getting property values, not for setting them:

    • For instance, the following fails:

      # ERROR: "The property 'foo' cannot be found on this object..."
      @(@{ foo = 1 }, @{ foo = 2 }).foo = 42
      
      • However, due to the unwrapping behavior, if the result happens to be only a single object, updating a property of that single object happens to work (which is why your assignment worked in principle, but failed due to an incompatible operand):

        # OK, but only because the single-element array wrapper
        # is effectively ignored.
        @(@{ foo = @{ bar = 1 } }).foo.bar = 42
        
    • Preventing setting is deemed by-design, to prevent inadvertent changes; either way, the error message is confusing - see GitHub issue #5271 for a discussion and a workaround.

Sign up to request clarification or add additional context in comments.

2 Comments

You're a legend, I must admit I do feel a little embarrassed now as i've been doing a lot of searching around for this and none have worked as of yet. Very good to know that's now working perfectly and great to know for the future! Thanks a lot!
@Russeller, glad to hear it helped - please see my update, which explains the behavior in more detail and provides background information. No need to feel embarrassed at all - this is tricky stuff, both for newcomers and experienced users (even though I'm familiar with the underlying problem, it took me a while to spot it in your code).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.