PowerShell and Hashtable oddities

Hashtables are IEnumerable, but they don’t behave that way in PowerShell … this seems to cause all sorts of odd behavior and such, so I thought I’d write up all the examples I can think of in one place. That means this post is going to be a little bit rambling, so please bear with me.

PowerShell enumerates IEnumerable


add-type @"
<code lang="
csharp">
using System.Collections;
public class enumer : IEnumerable {
  public IEnumerator GetEnumerator() {
    for(int i = 0; i < 10; i++) {
     yield return i;
   }
  }
}
"
@

$e -is [Collections.IEnumerable]      # is true
$e = new-object enumer
$e                                    # will output 0..9 to the console
$e.GetEnumerator()                    # will output the same
$e | measure-object                   # will show a count of 10.

$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}

$table -is [Collections.IEnumerable]  # is true too!

$table                                # will output three DictionaryEntry items
$table.GetEnumerator()                # will output the same thing
$table.Count                          # will output 3
$table | measure-object               # will output 1! WHAT!?
 

It turns out that in the case of Hashtables, PowerShell does NOT enumerate them into the pipeline. Instead, it passes the entire Hashtable object. Of course, nobody realizes this … because the ouput cmdlets unwrap them (what?!).

But there are bugs caused by special treatment

The first bug is in Add-Member, which doesn’t work on Hashtables until you’ve already used the Hashtable.


$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}
Add-Member -in $table NoteProperty Quiz "Surprise, hope you're ready!"
$table.Quiz  # It's not there! There is NO OUTPUT
Add-Member -in $table NoteProperty Quiz "Surprise, hope you're ready!"
$table.Quiz  # This time it works ...
 

NOT ONLY does Add-Member not work the first time, it’s not just a matter of calling it twice: you just have to try to access something in the hashtable before you can use Add-Member on it:


$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}
Add-Member -in $table NoteProperty Puzzle "How on earth does this work?"
Add-Member -in $table NoteProperty Quiz   "Surprise, hope you're ready!"
$table.Quiz  # It's not there! There is NO OUTPUT
Add-Member -in $table NoteProperty Quiz   "Surprise, hope you're ready!"
Add-Member -in $table NoteProperty Puzzle "How on earth does this work?"
$table | gm -type NoteProperty | %{ $_.Name }  # Output: Puzzle, Quiz
 

Here’s another buggy manifestation, in the Formatting cmdlets. This time, the Format-* cmdlets unroll the hashtable … to make it look like it’s being enumerated the way it should be.


$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}
## Prime it, and then Add-Member won't work
$table.PrimeTheHashtableSoWeCanAddMember
Add-Member -in $table NoteProperty Quiz   "Surprise, hope you're ready!"

## Note how Measure-Object and Get-Member operate on the HASHTABLE
## There's only a single item, of course...
$table | measure-object | %{$_.Count}  
## And we have 7 Properties, plus the Quiz NoteProperty
$table | get-member -type Properties | %{ $_.Name }

## But Format-List shows the properties of the ITEMS
## So we think we can list those properties like:
$table | format-list *
 

Clearly, the Format-* cmdlets have magic code that unwraps hashtables. Which just leads to even more confusion: $table looks the same (in the console output) as $table.GetEnumerator() ... but it doesn’t behave the same way, EXCEPT to the format cmdlets.

PowerShell 2.0 uses Hashtables more

In PowerShell 2.0, the PowerShell team is adding another special feature based on hashtables (which appears at first to be based on IEnumerables):

Jeffrey Snover gave an example of splatting in his PDC presentation. Splatting is where a collection is unwrapped so that you can take an array of values and pass one to each parameter of a cmdlet or function. But in Jeffrey Snover’s demo, he splatted a hashtable. Basically, the hashtable keys are matched up to parameter names as though they had been specified by name. That made me wonder why splatting can’t work with custom objects, but after investigating a bit, I’m actually frustrated with the inconsistency of how hashtables are treated in Posh.

  • In the splatting scenario they are unrolled as a collection of named parameters …
  • If you pipe them, they’re treated as a single object instead of being unrolled …
  • Even though you can access hashtable items using dotted property syntax, you can’t use them to set ValueFromPipelineByPropertyName values, because they aren’t really properties.

The new splatting feature seems to only work with simple arrays and hashtables … adding yet another scenario where the hashtable is being treated specially (even though it doesn’t need to be: if they just splatted IEnumerable, we could work with List, and any IEnumerable of DictionaryEntry objects, or KeyValuePair could be matched by name … that would make hashtables work, but it would also let you use the more powerful generic collections, etc.

You know what would be cool? If I could splat any object (like a custom PSObject that I have added members to), and have it’s property names matched to parameter names as though all the parameters had ValueFromPipelineByPropertyName set.

You know what would be really cool? If I could specify that I want pipeline objects splatted, forcing ALL parameters to be treated as ValueFromPipelineByPropertyName, without needing to use: ForEach-Object { Test-Splatting @_ } … maybe a syntax like: Get-HashTablesToSplat | Test-Splatting @@ …

Similar Posts:

One thought on “PowerShell and Hashtable oddities”

Comments are closed.