Powershell: Creating and Using Nested Hash Tables

I was working on a new provisioning script for my work for creating new users, creating their home drives, and blah blah blah (that’s the technical term). During this while adding error detection and resolution, in dawned on me that one of the things I would like to do is get an email that contained the users that had issues or failed, and also I would like the email to contain each error for each user — but how was I going to do it?

Batman pensively rubbing his chin

Wait — I got it! Hash tables! Arrays! Something to that effect!

So I began my journey into figuring out how I was going to accomplish this.

The first place to start is the PowerShell help file (aka, “help about_hash_tables -full”). That certainly got me going with figuring out the details about it, but I also needed to read up the help files on arrays, because I began to slowly figure out that you can’t really — or at least it appears this way to me — nest hash tables within hash tables (I welcome being wrong about this). However, arrays can be nested within arrays. So to the help file on arrays I went (“help about_arrays -full”).

Again, not quite enough info for me, so the following articles REALLY helped enlighten me:

However, what I wasn’t really understanding was how to nest the arrays and hash tables two or more orders below. Maybe I just didn’t read the material well, or maybe I just had to get to work doing it.

So after some time and further reading, I figured it out. The gist of it is that you initialize an array, then in your loop initialize another array for a loop, then create a hash table that has the properties and values of the properties, create a PS object that has their properties defined as the hash table, then do the same process for the parent loop, and voila!

Ok, it’s not so voila, and that was a mouthful, so below is script I put together to explain it, with comments!

# Import CSV File Containing Users
$users = Import-CSV users.csv

# Initiate array for collecting all errors for all users
$userErrors = @()

foreach ($user in $users) {
    # Grab user from each row in the CSV file
    $username = $user.user
    # Another list or source of content that I use for the nested hash table
    $numbers = Get-Content numbers.txt

    # Initiate array to collect individual errors 
    $objErrors = @()
    
    foreach ($number in $numbers) {
        # Create an ordered hash table of "errors" that will consist of object properties
        # and their values. 
        $errorLogging = [ordered]@{
            # Entry on left of assignment (equal sign "=") is property, entry on right is the value
            'Number' = $number
            # Just some randomized numbers I grabbed from somewhere online
            'Error' = -join ((48..57) + (97..122) | Get-Random -Count 32 | % {[char]$_})
        }
        # The secret sauce: we create and ADD a new Powershell object and define the properties
        # of the object with the variable "$errorLogging". In other words, as we loop through
        # "$numbers", we create a new object in $objErrors that contains the number and the
        # "Error" that we're trying to capture.
        $objErrors += New-Object -TypeName psobject -Property $errorLogging
    }
    # Just like the numbers loop above, we're going to define a new set of object properties
    # via a hash table, but this time we're creating an array of users that will also contain
    # a nested table of the errors associated with each user. 
    $userErrorLogging = [ordered]@{
        'Username' = $username
        'Errors' = $objErrors
    }
    # Take the defined properties above and add a new object to the "$userErrors" array
    $userErrors += New-Object -TypeName psobject -Property $userErrorLogging
}

## Totally optional below. I used it for testing.

# Creating some HTML output
$message = foreach ($user in $userErrors) {
    Write-Output "<h3>$($user.Username)</h3>"
    foreach ($e in $user.Errors) {
        Write-Output @"
        <p style="margin-left: 1em;">$($e.Number)</p>
        <p style="margin-left: 2em;">$($e.Error)</p>
"@
    }
}
# Creating an Internet Explorer object to use to open the HTML file
# NOTE: this will not if you haven't opened IE yet. So maybe a different browser
# is appropriate here, but this just works. I'm lazy.
$ie = New-Object -com "InternetExplorer.Application"
# Output the message as HTML
$message | Out-File -FilePath test.html
# Open the HTML file in Internet Explorer
$ie.navigate("test.html")

I don’t profess to be the expert on this, but it’s PowerShell, so there’s many paths to same destination — at least that’s what Don Jones says. :-p

Cheers!

Leave a Reply

Your email address will not be published. Required fields are marked *