Extending Hashtables functionalities in PowerShell by using classes

Extending Hashtables functionalities in PowerShell by using classes

Some time ago I was wondering if it was possible to have a multiple dictionary in PowerShell, so as an experiment, let’s see what we can do about this. I’m no developer, so might be there is a better, more practical answer to this, might be something already existing within the .Net framework. But the only answer I found, was to build one. Why? Well, essentially wanted to see if I could do it, but besides that there might be a few scenarios where it would be useful to have a multiple dictionary, will cover that later.

Hashtable basics

(feel free to skip this part if you are already familiar with Hashtables)

First thing first, what’s a multiple dictionary and how is it different from a Hashtable? (if we are 100% purists, Hashtables are not proper dictionaries, but for this case let’s consider them as such).

A dictionary is a structure that holds pairs of keys (i.e. words) and values (i.e. definitions). Keys cannot be repeated in the structure. The difference with multiple dictionaries, is that each key (still unique to the structure), can have more than one value associated, ideally each of the values associated to the key should be unique to the key.

Hashtables are defined in PowerShell by any of these ways:

#Most simple way to define a hash table

$hash1=@{"dog"="good";"cat"="cute"}
$hash1.Add("elephant","big")

#Define it by using the direct reference to the Hashtable class within the .Net Framework

$hash2=[System.Collections.Hashtable]::new()
$hash2.Add("dog","good")

#Define it empty with same notation as $hash1 and then add entries

$hash3=@{}
$hash2.Add("dog","good")

And they look like this:
So by looking at $hash1, we have 3 pairs of strings. But does it have to be always strings? Let’s take a look at the Add() method.

As you can see, both parameters are typed as System.Object, therefore there is no restriction to use String or any other data type, so what if instead of passing a String we give an array as a value? Yes, you can do that, but it poses a couple of problems, regular arrays are not very friend with Add() and Remove() methods, so you are limited to the “+=” operator. And what if first I added a value as a String and then I want to add a second value to that key? It just won’t work, as it is a String, it will append the new text to the end of the existing one. See here:

Creating a Multiple Dictionary

Many times in PowerShell when I need to build something, I think directly in Custom Objects and Functions, they’ve been around for so long and they are easy to write. But in this case that didn’t sound like the best approach, Classes instead, sounded like the right tool for the job since later, when instantiating an object from it, we will have both properties (values) and methods in the same object.

Yet, building a Class from scratch looked like too much of an effort, still we can create a new class and inherit the methods and properties from the Hashtable class, this gives us a huge advantage to extend the functionality of an already existing component. So by doing this my newly created class will be the same as a Hashtable:

class ExtendedHashtable : Hashtable {
}

So if we try to use it as a regular Hashtable we will see it works perfectly:

So I can try to override the Add() and Remove() methods with my own, but where do I find what these methods do? I’ll recommend you here to take a read at the following post PowerShell Deconstructed (by Stephen Owen), where he explains how to dive into the assemblies behind PowerShell. By using dotPeek (the tool Stephen recommends for decompiling DLLs), found out that the Add() method only calls to an Insert() method, so this means I could add some extra logic to my customized Add(), unfortunately the Insert() method is private, so I cannot inherit it (at least with my limited programming skills I think it is not possible). If you are interested on knowing which DLL you have to dive into, you can get it by running:


[Hashtable].Assembly

Since see no easy way to override the Add() method with a new one. Instead I’ve decided to create 3 new methods to extend the native Hashtable functionlity:

  1. ExtendedAdd: this method will receive a key and, either a value or an array of values. If the key doesn’t exist, the value(s) are loaded into an array list and the pair Key / Arraylist is added to the Hashtable. If the key exists, the values are added to the already existing Arraylist.
  2. StrictAdd: this method will receive a key and, either a value or an array of values. If the key doesn’t exist, the value(s) are loaded into an Arraylist (eliminating duplicates) and the pair Key / Arraylist is added to the Hashtable. If the key exists, the values that are not loaded already, added to the already existing Arraylist.
  3. ExtendedRemove: this method will receive a key and, either a value or an array of values. It will remove the entered values from the Arraylist associated with the key, one the Arraylist is empty, they key gets eliminated.

There is only one problem I’ve not been able to address, which is by implementing this code the Key becomes case sensitive, which is not much of a big deal, but good to keep in mind.

class ExtendedHashtable : Hashtable {
    [void] ExtendedAdd ([system.object]$Key,[System.Object[]]$Values) {
        if( -not ($this.get_Keys() -contains $Key) ) {
            $myArrayList=[System.Collections.Arraylist]::New()
            $this.Add($key,$myArrayList)
        } 

        foreach($value in $values) {
            $this[$key].Add($value)
        }
    }

    [void] StrictAdd ([system.object]$Key,[System.Object[]]$Values) {
        if( -not ($this.get_Keys() -contains $Key) ) {
            $myArrayList=[System.Collections.Arraylist]::New()
            $this.Add($key,$myArrayList)
        } 

        foreach($value in $values) {
            if( -not ($this[$key] -contains $value)){
                $this[$key].Add($value)
            }
        }
    }

    [void] ExtendedRemove ([system.object]$Key,[System.Object[]]$Values) {
        if($this.get_Keys() -contains $Key) {
            foreach($value in $values) {
                do {
                    $this[$Key].Remove($value)
                } While ($this[$Key] -contains $value)
            }
            if($this[$key].count -eq 0) {
                $this.Remove($key)
            }
        }
    }
}

Applicability

So now that I know this is possible, the question is, what is it useful for. I can think in two primarily useful scenarios:

  • When doing splatting
  • Classifying objects for later processing

The second scenario is very specific, but splatting is a technique I’ve used quite a lot lately, which is essentially parametrizing a CmdLet with a Hashtable instead of calling to its parameters. An example of splatting could be:

#Calling Get-NetIPAddress using parameters too show the IPv4 loopback IP address

Get-NetIPAddress -Type Unicast -AddressFamily IPv4 -IPAddress 127.0.0.1

#Splatting consist in creating a Hashtable with the pairs of parameter/value, and then invoking the command passing the Hashtable as an argument, particularly useful if you need to decide which parameters to use during your script runtime

$splattingHash=@{
    "Type"="Unicast"
    "AddressFamily"="IPv4"
    "IPAddress"="127.0.0.1"
}

Get-NetIPAddress @splattingHash

So a multiple dictionary might result useful when we have a CmdLet or function that supports multiple values passed for a single parameter, to show that I’ve written a simple function:

function Test-Splatting {
    param (
        [String]$Pet,
        [String[]]$PetAttributes
    )

    foreach ($Attribute in $PetAttributes) {
        Write-Output "$Pet is $Attribute"
    }
}

So if we build an Extended Hashtable and then use it to call our newly defined function, it would look like this:

Conclusion

When started working on this very small experiment the idea was to understand how to use classes, and I think I’ve got to understand as well where to use them. While custom objects and functions are great tools for scripting, classes are excellent tools to either, extend functionalities of already existing classes, as we have done here, or when you need to represent a single entity in your script, with its methods and properties.

Hernán J. Larrea

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.