How can I enumerate a hashtable as key-value pairs / filter a hashtable by a collection of key values How can I enumerate a hashtable as key-value pairs / filter a hashtable by a collection of key values powershell powershell

How can I enumerate a hashtable as key-value pairs / filter a hashtable by a collection of key values


You have some options here.

Enumerating through keys:

foreach ($key in $var.Keys) {    $value = $var[$key]    # or    $value = $var.$key }

Enumerating key-value pairs (which you've discovered, but may not be using effectively):

foreach ($kvp in $var.GetEnumerator()) {    $key = $kvp.Key    $val = $kvp.Value}


To complement briantist's helpful answer by focusing on filtering a hashtable by an array of key values (PSv3+ syntax):

# Sample hashtable.$ht = @{ one = 1; two = 2; three = 3 }# Filter it by an array of key values; applying .GetEnumerator() yields an array# of [System.Collections.DictionaryEntry] instances, which have# a .Key property and a .Value property.$ht.GetEnumerator()  | ? Key -in 'one', 'two'# Similarly, the *output* - even though it *looks* like a hashtable - # is a regular PS *array* ([Object[]]) containing [System.Collections.DictionaryEntry]# entries (2 in this case).$arrFilteredEntries = $ht.GetEnumerator()  | ? Key -in 'one', 'two'$arrFilteredEntries.GetType().Name # -> Object[]

To further process the matching key-value pairs, simply pipe to % (ForEach-Object) and access $_.Key and $_.Value (value):

$ht.GetEnumerator()  | ? Key -in 'one', 'two' |   % { "Value for key '$($_.Key)': $($_.Value)" }

The equivalent command using a more efficient foreach loop instead of the pipeline:

foreach ($key in $ht.Keys) {   if ($key -in 'one', 'two') { "Value for key '$($key)': $($ht.$key)" }}

Note: In PSv2:
* operator -in is not supported, but you can use -contains instead with the operands swapped:
'one', 'two' -contains $key
* in the pipeline, use Where-Object { 'one', 'two' -contains $_.Key }

With the sample hashtable, this yields:

Value for key 'two': 2Value for key 'one': 1

Note how the key order in the output differs from the definition order; in PSv3+, you can create ordered hashtables ([ordered] @{ ... }) to preserve the definition order.

The key-filtering technique used above is not limited to filtering by literal key arrays; any (string) collection will do as the RHS of the -in operand, such as the .Keys collection of a different hashtable:

# Sample input hashtable.$htInput = @{ one = 1; two = 2; three = 3 }# Hashtable by whose keys the input hashtable should be filtered.# Note that the entries' *values* are irrelevant here.$htFilterKeys = @{ one = $null; two = $null }# Perform filtering.$htInput.GetEnumerator()  | ? Key -in $htFilterKeys.Keys |   % { "Value for key '$($_.Key)': $($_.Value)" }# `foreach` loop equivalent:foreach ($key in $htInput.Keys) {  if ($key -in $htFilterKeys.Keys) { "Value for key '$($key)': $($htInput.$key)" }}

The result is the same as in the example with the static filter-keys array.

Finally, if you want to filter a hashtable in place or create a new hashtable with only the filtered entries:

# *In-place* Updating of the hashtable.# Remove entries other than the ones matching the specified keys.# Note: The @(...) around $ht.Keys is needed to clone the keys collection before# enumeration, so that you don't get an error about modifying a collection# while it is being enumerated.foreach ($key in @($ht.Keys)) {   if ($key -notin 'one', 'two') { $ht.Remove($key) } } # Create a *new* hashtable with only the filtered entries.# By accessing the original's .Keys collection, the need for @(...) is obviated.$htNew = $ht.Clone()foreach ($key in $ht.Keys) {   if ($key -notin 'one', 'two') { $htNew.Remove($key) }} 

As an aside:

The default output format for [System.Collections.DictionaryEntry] (and thus hashtables ([System.Collections.Hashtable]) uses column name Name rather than Key; Name is defined as an alias property of Key added by PowerShell (it is not part of the [System.Collections.DictionaryEntry].NET type definition; verify with
@{ one = 1 }.GetEnumerator() | Get-Member).