Scroll Top
Evotec Services sp. z o.o., ul. Drozdów 6, Mikołów, 43-190, Poland

A short story on PowerShell HashTables that beat me hard

HashTable

Today's story happened a few days back to me, and I think many people may not even know about some object behaviors in PowerShell (just like me). I know some of you may think this is basic PowerShell knowledge but even thou I've written a lot of different PowerShell modules I kind of skipped basics. That means I often find myself struggling with something that's obvious if you've done your due diligence before starting to work with the code. But that's typical me, playing with complicated stuff, failing at simple things.

Code that needed optimizations

Let's take a look at this shortcode below. It takes a parameter called Computer, and you can give multiple computers to it. Then in function depending on Computer name HashTable is returned. If you give it just one Computer one HashTable is returned. If multiple Computers are passed to function, multiple HashTables return.

function Get-DataBefore {
    param(
        [string[]] $Computer
    )
    $Values = foreach ($Comp in $Computer) {
        if ($Comp -eq 'Computer1') { 
            $MyHashTable = @{
                Display = 'OK'
                Data    = 'No'
                Value   = 10
            }
        } else {
            $MyHashTable = @{
                Display = 'OK'
                Data    = 'No'
                Value   = 7
            }            
        }
        $MyHashTable
    }
    $Values
}

As you may notice in the code above the only difference between 2 different HashTables is its Value. It's either 10 or 7.

Optimized code

I thought to myself – Let's optimize it. It looks horrible (code in this article is simplified) that I am wasting so much PowerShell lines for one value change.

function Get-DataAfter {
    param(
        [string[]] $Computer
    )
    $MyHashTable = @{
        Display = 'OK'
        Data    = 'No'
    }
    $Values = foreach ($Comp in $Computer) {
        if ($Comp -eq 'Computer1') {
            $MyHashTable.Value = 10
        } else {
            $MyHashTable.Value = 7
        }    
        $MyHashTable
    }
    $Values
}

Looks good right? The code is optimized, fewer lines and we're done. The only problem is I was getting wrong results further down the road in a completely different place. It took me a while to understand what is happening and how the code you see above is my problem. Can you see it?

Working code

That's totally wrong! It's not what I expected. Do you know how to fix it?

function Get-DataAfter {
    param(
        [string[]] $Computer
    )
    $MyHashTable = @{
        Display = 'OK'
        Data    = 'No'
    }
    $Values = foreach ($Comp in $Computer) {
        if ($Comp -eq 'Computer1') {
            $MyHashTable.Value = 10
        } else {
            $MyHashTable.Value = 7
        }    
        $MyHashTable.Clone()
    }
    $Values
}

Fortunately for me, my brain remembered something about Cloning. I didn't really pay attention to it until now. After fixing line 15 I decided that I need to test a few things to confirm my understanding of HashTables and PSCustomObject. I've suspected that the way I used those functions before could be sometimes optimized or used in different ways.

Lets learn on my mistakes

I went ahead and created three simple functions that were supposed to help me understand how things work and how I can use them in the future.

function Set-HashTable {
    param(
        [System.Collections.IDictionary] $Hash,
        [string] $Property,
        [Object] $Value
    )
    $Hash.$Property = $Value
    # no return values
}
function Set-CustomObject {
    param(
        [PsCustomObject] $Custom,
        [string] $Property,
        [Object] $Value
    )
    $Custom.$Property = $Value
    # no return values
}
function Set-CustomInt {
    param(
        [int] $MyInt,
        [int] $Value
    )
    $MyInt = $Value
}

I've then defined three variables. One int, one PSCustomObject, and one HashTable.

[int] $i = 5

$CustomObject = [PsCustomObject]@{
    Name  = 'Display'
    Value = 5
}

$HashTable = @{
    Name  = 'Display'
    Value = 5
}

Now I simply overwrite those values by using the first static assignment and then updating each variable inside a function.

$HashTable.Value = 7
$CustomObject.Value = 7
$i = 7

Set-HashTable -Hash $HashTable -Property 'Value' -Value 8
Set-CustomObject -Custom $CustomObject -Property 'Value' -Value 8
Set-CustomInt -MyInt 8

# What value is this?
$HashTable.Value 
$CustomObject.Value 
$i

Can you guess what above code returns? I'll leave it up to you, but till a few days ago I've always assumed that when I pass a variable to a function to get it back, I've to return it. So when I pass a HashTable, and I want to get it back I would return full HashTable and assign it back to either new variable or to the same variable I've passed to function. But the way it works above (and the code above proves it) you can pass HashTable to a function, update its values and get it back without assigning anything back. Isn't it cool? I know you're probably saying YOU NOOB everyone knows it! Well, I didn't know. But that's how I roll. Only hardcore stuff, basics are for losers right? Till they bite you hard! So learn from my mistakes and read the documentation before jumping in, you may save yourself time later on!

Repeat after me - I am not a TOTAL noob!

Just so you don't think I'm a total noob for not knowing basics take a look at some progress I did on PSWriteHTML project.

There's still a long way to go as I rewrite most of the code from old fork ReportHTML and deciding what to include that will have some usefulness for general PowerShell community.