Introducing Darth Enumerator, PowerShell TIE Fighter |e| Operator

by Klaus Graefensteiner 10. April 2009 04:05

In a Galaxy far, far way…

Master Jeffrey and Master Bruce designed the Scalarity Shield to protect the most powerful power source of the alliance. The Hashtables. Only with great effort would the empire be able to penetrate it and tap into this great source of energy. We, The Dark Side, after months of research, discovered finally an elegant way to strip the Scalarity form the Hashtables and plug them without any changes into our power grids. Darth Enumerator is the code name of a new system that will finally bring peace to the galaxy. The system consists of advanced versions of TIE Fighter space ships called |e| (Pipe-e-Pipe) and their droid operators. This droid is able to perform the exact sequence of maneuvers that will neutralize the Scalarity Shields and provide direct access to the Hash Table cells.

One serendipitous side effect of this research is the invention of Synthetic Force. TIE fighters equipped with it (|e-f|) will skin any kind of Scalarity shield of any object and return pure energy.

Pipe-e-Pipe TIE Fighter

Figure 1: See the energy? Powered by Synthetic Force and operated by Darth Enumerator. TIE-Fighter Model |e-f|

Motiviation

A few month ago I blogged about how PowerShell's implementation of Hashtables caused some kind of confusion: When PowerShell hashtable magic backfires. I didn’t like that fact that PowerShell treated the Hashtable different than an Array when processing their collection elements downstream. The Get-Enumerator cmdlet described in this post changes the Hashtable behavior. Functionality that I expected will be applied implicitly and side effects that surprised me now require explicit action.

Design

The design of the Get-Enumerator Cmdlet is described in the following flow chart:

Get-Enumerator Flow Chart

Figure 2: Get-Enumerator Flow Chart

First the Cmdlet checks whether the piped-in object is a Hashtable. If yes, it will call .GetEnumerator() and return. If the object is not a Hashtable, then it will check the existence of the –Force switch. If –Force is set, then it will throw an error indicating that the passed-in object is not a Hashtable. If –Force is passed in, then the function will verify whether the input object is an IEnumerable. If it isn’t, it will look for the –Strict switch. If –Strict is set, then the Cmdlet will throw an error indicating that the object is not an IEnumerable. If –Strict is not set, the Cmdlet will return the input object untouched. If indeed the input object is an IEnumerable, then the function also checks whether the input object is a String. If it is not a String, then the Cmdlet will call .GetEnumerator() and return. If it is a String, it will look for the –PreserveString switch. If it is present, then it will return the String as one object, without calling .GetEnumerator(). Otherwise it will call .GetEnumerator() on the String and return a collection of characters.

Implementation

I first considered to implement this Advanced Function using the –is type check operator, then I played with the idea to do the type filtering using Parameter Sets.

Type filtering using -is operator

   1: function Get-Enumerator 
   2: {
   3:     [CmdletBinding()]
   4:     PARAM(
   5:     
   6:         [Switch] $Force, 
   7:         
   8:         [Switch] $Strict,
   9:         
  10:         [Switch] $PreserveString,
  11:     
  12:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
  13:         [Alias("Input","Hashtable")]
  14:         [Object] $Enumerable
  15:     
  16:     
  17:     )
  18:     Process{
  19:     
  20:         if( $Enumerable -is "System.Collections.Hashtable")
  21:         {
  22:             return $Enumerable.GetEnumerator()
  23:         }
  24:         
  25:         if ( $Enumerable -is "System.String" -and $PreserveString -and $Force)
  26:         {
  27:             return $Enumerable
  28:         }
  29:         
  30:         if( $Enumerable -is "System.Collections.IEnumerable" -and $Force )
  31:         {
  32:             return $Enumerable.GetEnumerator()
  33:         }
  34:         
  35:         if($Force -and -not $Strict)
  36:         {
  37:             return $Enumerable
  38:         }
  39:             
  40:         if ($Force -and $Strict)
  41:         {
  42:             throw("Pipeline input is not an IEnumerable")
  43:         }
  44:         
  45:         throw("Pipeline input is not a HashTable")
  46:     
  47:     }# End Process
  48: }
  49:  
  50: set-alias e Get-Enumerator
  51: set-alias so Sort-Object

Type filtering using Parameter Binding

   1: function Get-Enumerator 
   2: {
   3:     [CmdletBinding(DefaultParameterSetName="TextInput")]
   4:     PARAM(
   5:     
   6:         [Switch] $Force, 
   7:         
   8:         [Switch] $Strict,
   9:         
  10:         [Switch] $PreserveString,
  11:     
  12:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="HashtableInput")]
  13:         [System.Collections.Hashtable] $HashtableInput,
  14:         
  15:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="TextInput")]
  16:         [String] $TextInput,
  17:         
  18:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="IEnumerableInput")]
  19:         [System.Collections.IEnumerable] $IEnumerableInput,
  20:         
  21:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ObjectInput")]
  22:         [Object] $ObjectInput
  23:     
  24:     
  25:     )
  26:     Process{
  27:     
  28:         switch ($PsCmdlet.ParameterSetName) 
  29:         { 
  30:             "HashtableInput"    
  31:             { 
  32:                 return $HashtableInput.GetEnumerator(); 
  33:             } 
  34:             
  35:             "TextInput"       
  36:             { 
  37:                 if (-not $Force)
  38:                 {
  39:                     throw("Pipeline input is not a HashTable")
  40:                 }
  41:                 if ($PreserveString)
  42:                 { 
  43:                     return $TextInput
  44:                 } 
  45:                 else
  46:                 { 
  47:                     return $TextInput.GetEnumerator() 
  48:                 }
  49:             }
  50:             
  51:             "IEnumerableInput"
  52:             {
  53:                 if (-not $Force)
  54:                 {
  55:                     throw("Pipeline input is not a HashTable")
  56:                 }
  57:                 return $IEnumerableInput.GetEnumerator()
  58:             }
  59:             
  60:             "ObjectInput"
  61:             {
  62:                 if (-not $Force)
  63:                 {
  64:                     throw("Pipeline input is not a HashTable")
  65:                 }
  66:                 if ($Strict)
  67:                 {
  68:                     throw("Pipeline input is not an IEnumerable")
  69:                 }
  70:                 return $ObjectInput
  71:             }
  72:         } 
  73:     
  74:     }# End Process
  75: }
  76:  
  77: set-alias e Get-Enumerator
  78: set-alias so Sort-Object

While working on the first implementation I created a hand-rolled unit test harness that immediately exposed the limitations of the second approach: The PowerShell parameter binding can't distinguish between a base type and a derived type. In my example between a String and an IEnumerable or a Hashtable and an IEnumerable.

Usage and Unit tests

   1: CLS
   2:  
   3: . C:\SCHREIBTISCH\SVN\PSVP\DarthEnumerator\Darth-Enumerator.ps1
   4: #. C:\SCHREIBTISCH\SVN\PSVP\DarthEnumerator\Lord-Enumerator.ps1
   5:  
   6: Get-Help Get-Enumerator -Full
   7:  
   8:  
   9: "Hashtable Tests"
  10:  
  11: "----- 1"
  12: $Team = @{4 = "Joe"; 2 = "Steve"; 12 = "Tom"}
  13: $Team.GetType().Fullname
  14:  
  15: "----- 2"
  16: $Team | Sort-Object
  17:  
  18: "----- 3"
  19: $Team | Get-Enumerator
  20:  
  21: "----- 4"
  22: $Team.GetEnumerator() | Sort-Object -Property "Key"
  23:  
  24: "----- 5"
  25: $Team | Get-Enumerator | Sort-Object -Property "Key"
  26:  
  27: "----- 6"
  28: $Team |e| so Key
  29:  
  30: "----- 7"
  31: $Team | Get-Enumerator -Force | Sort-Object -Property "Key"
  32:  
  33: "----- 8"
  34: $Team |e -f| so Key
  35:  
  36: "----- 9"
  37: $Team | Get-Enumerator -Force -Strict | Sort-Object -Property "Key"
  38:  
  39: "----- 10"
  40: $Team |e -f -s| so Key
  41:  
  42: "Array Tests"
  43:  
  44: "===== 1"
  45: $RosterNames = "Joe", "Steve", "Tom"
  46: $RosterNumbers = 4,2,12
  47: $RosterNames.GetType().Fullname
  48: $RosterNumbers.GetType().Fullname
  49:  
  50: "===== 2"
  51: $RosterNumbers
  52: $RosterNumbers |e -f| so
  53:  
  54: "===== 3"
  55: if ($true)
  56: {
  57:     trap {"Expected Error: Pipeline input is not an IEnumerable - Actual Error: $_"; continue} 
  58:     $RosterNumbers |e -f -s| so
  59: }
  60:  
  61: "===== 4"
  62: if ($true)
  63: {
  64:     trap {"Expected Error: Pipeline input is not a HashTable - Actual Error: $_"; continue} 
  65:     $RosterNames |e| so 
  66: }
  67:  
  68: "===== 5"
  69: if ($true)
  70: {
  71:     trap {"Expected Error: Pipeline input is not a HashTable - Actual Error: $_"; continue} 
  72:     $RosterNumbers |e| so 
  73: }
  74:  
  75: "===== 6"
  76: $RosterNumbers | Get-Enumerator -Force | Sort-Object
  77:  
  78: "===== 7"
  79: $RosterNumbers | e -f | so # Interesting: Calling GetEnumerators on the string
  80:  
  81: "===== 8"
  82: $RosterNames
  83: $RosterNames | Get-Enumerator -Force | Sort-Object
  84:  
  85: "===== 9"
  86: $RosterNames | e -f | so # Interesting: Calling GetEnumerators on the string
  87:  
  88: "===== 10"
  89: $RosterNames | e -Force -PreserveString| so
  90:  
  91: "===== 11"
  92: $RosterNames | e -Force -PreserveString -Strict| so
  93:  
  94: "===== 12"
  95: $RosterNames | e -f -p | so
  96:  
  97: "===== 13"
  98: $RosterNames | e -f -p -s| so
  99:  
 100:  
 101: "ArrayList Tests"
 102:  
 103: "..... 1"
 104: $RosterNameList = New-Object -TypeName "System.Collections.ArrayList"
 105: $RosterNames | foreach-object {$RosterNameList.Add($_)}| Out-Null
 106: $RosterNameList
 107: $RosterNameList.GetType().Fullname
 108:  
 109: $RosterNumberList = New-Object -TypeName "System.Collections.ArrayList"
 110: $RosterNumbers | foreach-object {$RosterNumberList.Add($_)} | Out-Null
 111: $RosterNumberList
 112: $RosterNumberList.GetType().Fullname
 113:  
 114: "..... 2"
 115: $RosterNumberList
 116: $RosterNumberList |e -f| so
 117:  
 118: "..... 3"
 119: if ($true)
 120: {
 121:     trap {"Expected Error: Pipeline input is not an IEnumerable - Actual Error: $_"; continue} 
 122:     $RosterNumberList |e -f -s| so #ArrayList gets implicitly converted to an array (ToArray()) and treated as such
 123: }
 124:  
 125: "..... 4"
 126: if ($true)
 127: {
 128:     trap {"Expected Error: Pipeline input is not a HashTable - Actual Error: $_"; continue} 
 129:     $RosterNumberList |e| so
 130: }
 131:  
 132: "..... 5"
 133: if ($true)
 134: {
 135:     trap {"Expected Error: Pipeline input is not a HashTable - Actual Error: $_"; continue} 
 136:     $RosterNameList |e| so 
 137: }
 138:  
 139: "..... 6"
 140: $RosterNumberList | Get-Enumerator -Force | Sort-Object
 141:  
 142: "..... 7"
 143: $RosterNumberList | e -f | so
 144:  
 145: "..... 8"
 146: $RosterNameList
 147: $RosterNameList | Get-Enumerator -Force | Sort-Object
 148:  
 149: "..... 9"
 150: $RosterNameList | e -f | so
 151:  
 152: "..... 10"
 153: $RosterNameList | e -Force -PreserveString| so
 154:  
 155: "..... 11"
 156: $RosterNameList | e -Force -PreserveString -Strict| so
 157:  
 158: "..... 12"
 159: $RosterNameList | e -f -p | so
 160:  
 161: "..... 13"
 162: $RosterNameList | e -f -p -s| so
 163:  
 164:  
 165: "SortedList Tests" #Doesn't have ToArray() function
 166:  
 167: "~~~~~ 1"
 168: $RosterNameList = New-Object -TypeName "System.Collections.SortedList"
 169: $RosterNames | foreach-object {$RosterNameList.Add($_, $_)}| Out-Null
 170: $RosterNameList
 171: $RosterNameList.GetType().Fullname
 172:  
 173: $RosterNumberList = New-Object -TypeName "System.Collections.SortedList"
 174: $RosterNumbers | foreach-object {$RosterNumberList.Add($_, $_)} | Out-Null
 175: $RosterNumberList
 176: $RosterNumberList.GetType().Fullname
 177:  
 178: "~~~~~ 2"
 179: $RosterNumberList
 180: $RosterNumberList |e -f| %{$_.Value}
 181:  
 182: "~~~~~ 3"
 183: $RosterNumberList |e -f -s| %{$_.Value}
 184:  
 185: "~~~~~ 4"
 186: if ($true)
 187: {
 188:     trap {"Expected Error: Pipeline input is not a HashTable - Actual Error: $_"; continue} 
 189:     $RosterNumberList |e| %{$_.Value}
 190: }
 191:  
 192: "~~~~~ 5"
 193: if ($true)
 194: {
 195:     trap {"Expected Error: Pipeline input is not a HashTable - Actual Error: $_"; continue} 
 196:     $RosterNameList |e| %{$_.Value} 
 197: }
 198:  
 199: "~~~~~ 6"
 200: $RosterNumberList | Get-Enumerator -Force | Sort-Object
 201:  
 202: "~~~~~ 7"
 203: $RosterNumberList | e -f | %{$_.Value}
 204:  
 205: "~~~~~ 8"
 206: $RosterNameList
 207: $RosterNameList | Get-Enumerator -Force | Sort-Object
 208:  
 209: "~~~~~ 9"
 210: $RosterNameList | e -f | %{$_.Value}
 211:  
 212: "~~~~~ 10"
 213: $RosterNameList | e -f -s | %{$_.Value}
 214:  
 215: "~~~~~ 11"
 216: $RosterNameList | e -Force -PreserveString| %{$_.Value}
 217:  
 218: "~~~~~ 12"
 219: $RosterNameList | e -Force -PreserveString -Strict| %{$_.Value}
 220:  
 221: "~~~~~ 13"
 222: $RosterNameList | e -f -p | %{$_.Value}
 223:  
 224: "~~~~~ 14"
 225: $RosterNameList | e -f -p -s| %{$_.Value}
 226:  

Deployment

There are two ways to add the |e| operator to your environment. The first one is to dot source it in your profile script. The other one is to create and import a module that contains the script that defines the operator. I am keeping it simple here and decided to copy it to my user’s profile that applies to all shells.

Dot sourcing in Profile

Loading Darth Enumerator From Profile 

Figure 3: Deploy the operator

Copy the Darth-Enumerator.ps1 script file into your user's Documents\WindowsPowerShell folder next to your profile.ps1 file. Edit your profile.ps1 file and dot source Darth-Enumerator.ps1.

Download

To teleport Darth Enumerator to your platform click the following link: DarthEnumerator.zip

Ausblick

I must admit that, besides discovering synthetic Force and bringing peace to the galaxy, this little adventure shed some light into many aspects of PowerShell.

Limitations of Parameter sets

I would have expected that the parameter binding algorithm would select the most specialized object, if objects have the same base class. But it doesn't. And using the default parameter set doesn't help here either, because it only can resolve one ambiguity. In my case there are two. It would be nice, if there would me a more extensible way to resolve ambiguities, besides using default parameter sets.

Extension Methods

Starting this project, I assumed that the distinction between an array and a Hashtable in PowerShell is the implementation of the IEnumerable interface. Now I know that there are two kinds of IEnumerables: There is one kind that PowerShell converts into Arrays before processing it in a pipeline for example an ArrayList. And another kind like a SortedList or a HashTable that get passed to the pipeline as scalar objects. My assumption was wrong and now there is some room for speculations. I wonder whether adding an extension method like ToArray() to the .NET Hashtable class, would let me pipe a Hashtable in PowerShell as Array of Dictionary Objects.

Lightweight Unit Test Framework

I am still looking for a lightweight Unit Test Framework for PowerShell functions. I haven’t found it yet.

Recurse parameter

To make Get-Enumerator even more powerful I am considering adding a -Recurse parameter that call .GetEnumerator() on nested IEnumerables.

Tags: , , ,

PowerShell | .NET Framework | Test Automation | Tips & Tricks

Comments are closed

About Klaus Graefensteiner

I like the programming of machines.

Add to Google Reader or Homepage

LinkedIn FacebookTwitter View Klaus Graefensteiner's profile on Technorati
Klaus Graefensteiner

Klaus Graefensteiner
works as Developer In Test and is founder of the PowerShell Unit Testing Framework PSUnit. More...

Open Source Projects

PSUnit is a Unit Testing framwork for PowerShell. It is designed for simplicity and hosted by Codeplex.
BlogShell is The tool for lazy developers who like to automate the composition of blog content during the writing of a blog post. It is hosted by CodePlex.

Administration

About

Powered by:
BlogEngine.Net
Version: 1.6.1.0

License:
Creative Commons License

Copyright:
© Copyright 2014, Klaus Graefensteiner.

Disclaimer:
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Theme design:
This blog theme was designed and is copyrighted 2014 by Klaus Graefensteiner

Rendertime:
Page rendered at 7/23/2014 1:08:26 PM (PST Pacific Standard Time UTC DST -7)