Categories: PowerShell

A short story on PowerShell HashTables that beat me hard

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.

This post was last modified on %s = human-readable time difference 10:16

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Upgrade Azure Active Directory Connect fails with unexpected error

Today, I made the decision to upgrade my test environment and update the version of…

1 month ago

Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster

Have you ever looked at your Active Directory and wondered, "Why do I still have…

2 months ago

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

7 months ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

11 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

1 year ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

1 year ago