3

So, I'm rather new to PowerShell and just can't figure out how to use the arrays/lists/hashtables. I basically want to do the following portrayed by Python:

entries = {
    'one' : {
        'id': '1',
        'text': 'ok'
    },
    'two' : {
        'id': '2',
        'text': 'no'
    }
}

for entry in entries:
    print(entries[entry]['id'])

Output:

1

2


But how does this work in PowerShell? I've tried the following:

$entries = @{
    one = @{
        id = "1";
        text = "ok"
    };
    two = @{
        id = "2";
        text = "no"
    }
}

And now I can't figure out how to access the information.

foreach ($entry in $entries) {
   Write-Host $entries[$entry]['id']
}

=> Error

mklement0
  • 382,024
  • 64
  • 607
  • 775
jeanat
  • 33
  • 3

2 Answers2

5

PowerShell prevents implicit iteration over dictionaries to avoid accidental "unrolling".

You can work around this and loop through the contained key-value pairs by calling GetEnumerator() explicitly:

foreach($kvp in $entries.GetEnumerator()){
    Write-Host $kvp.Value['id']
}

For something closer to the python example, you can also extract the key values and iterate over those:

foreach($key in $entries.get_Keys()){
    Write-Host $entries[$key]['id']
}

Note: You'll find that iterating over $entries.Keys works too, but I strongly recommend never using that, because PowerShell resolves dictionary keys via property access, so you'll get unexpected behavior if the dictionary contains an entry with the key "Keys":

$entries = @{
    Keys = 'a','b'
    a = 'discoverable'
    b = 'also discoverable'
    c = 'you will never find me'
}

foreach($key in $entries.Keys){ # suddenly resolves to just `'a', 'b'`
    Write-Host $entries[$key]
}

You'll see only the output:

discoverable
also discoverable

Not the Keys or c entries

Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • It is unfortunate that about_Hash_Tables doesn't mention `Get_Keys()` – Santiago Squarzon Feb 09 '22 at 14:23
  • 3
    @SantiagoSquarzon Thanks for volunteering for the documentation effort ;-) – Mathias R. Jessen Feb 09 '22 at 14:24
  • 1
    This is a good candidate for a canonical answer covering hashtable/dictionary entry enumeration. Can I suggest mentioning the term "hashtable" / "hash table" in the answer, and perhaps also the `IDictionary` interface as the common denominator? As for the backstory behind why an entry with key `Keys` unfortunately shadows the type-native `.Keys` property (ditto for `Values`), see [GitHub issue #7758](https://github.com/PowerShell/PowerShell/issues/7758). Note that, by contrast, the member-enumeration feature gets it right and gives precedence to type-native properties. – mklement0 Feb 10 '22 at 17:33
  • 2
    As for the documentation effort: An alternative to `.get_Keys()` / `.get_Values()` is `.PSBase.Keys` / `.PSBase.Values`, and `.PSBase.Keys` _is_ already mentioned in [about_Hash_Tables](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Hash_Tables): "If the key name collides with one of the property names of the HashTable type, you can use `PSBase` to access those properties. For example, if the key name is `keys` and you want to return the collection of Keys, use this syntax: `$hashtable.PSBase.Keys`" /cc @SantiagoSquarzon – mklement0 Feb 10 '22 at 17:36
3

To complement Mathias R. Jessen's helpful answer with a more concise alternative that takes advantage of member-access enumeration:

# Implicitly loops over all entry values and from each 
# gets the 'Id' entry value from the nested hashtable. 
$entries.Values.Id  # -> 2, 1

Note: As with .Keys vs. .get_Keys(), you may choose to routinely use .get_Values() instead of .Values to avoid problems with keys literally named Values.

mklement0
  • 382,024
  • 64
  • 607
  • 775