Having Fun With Nested Hash Tables: Building a Hard-coded DB

Having Fun With Nested Hash Tables: Building a Hard-coded DB

Introduction

Hash tables in PowerShell are, from a syntax standpoint, a scriptblock preceded by a '@' sign which enclose key value pairs. Now, key value pairs are a key with its corresponding value, or in more common words, a property name and a property value with an equal sign in the middle. Here is an example.

@{
    'FirstName' = 'John'
    'LastName' = 'Smith'
}

The hash table above has two keys: FirstName and LastName. And two values: John and Smith (respectively). If we assign a variable name to the table we can reference the keys to get their corresponding value.

$User = @{
            'FirstName' = 'John'
            'LastName' = 'Smith'
         }

PS C:\> $User.FirstName
John

Hash tables are frequently used this way, to store in a variable a set of related properties whose values can be retrieved by using the combination of '$HashTable.KeyName'.

Parameter splatting and custom properties are also common PowerShell structures that take advantage of hash tables but are out of the scope of this post. Splatting will likely be covered in upcoming posts though.

Nested Hash Tables

Now that we have discussed the basic concept of a hash table, I would like to introduce the nested hash tables. As you can probably imagine, a nested hash table is a hash table inside another one. Before we go any further, there are a few things to consider when using nested hash tables:

  1. They are not a replacement for input files or databases as data sources. E.g. xml, csv files or SQL databases.
  2. Hard-coding values is not recommended under most circumstances. It is not considered a good practice. Although hash tables can contain dynamic values, it is tempting to fill them up with static data.
  3. You should consider pros and cons of including a hash table in a script or function, since there could be a better approach.

That said, hash tables may be extremely useful when used properly. In fact, we can use nested hash tables to keep a small DB inside a script or function.

Examples

When trying to come up with a good example for nested hash tables, I thought of a list of desktop computer models and their respective specs. Then I decided I would use Dell computers to build my tables. I hope Dell won't mind, most data is made up so that's a disclaimer I feel I need to make.

Anyway, let's look at one hash table with details about the Dell Optiplex desktops.

$Optiplex = @{
                'Brand' = 'Dell'
                'Model' = 'Optiplex'
             }

In the example above, a variable stores the brand and model of a Dell Optiplex desktop computer. Now, let's suppose we want to add some specs like RAM, CPU, etc.

$Optiplex = @{
                'Brand' = 'Dell'
                'Model' = 'Optiplex'
                'Specs' = @{
                                'RAM' = '4 GB'
                                'CPU' = 'Intel i5'
                                'USB Ports' = '3'
                                'CD/DVD Drive' = 'No'
                                'HDD' = '320 GB - SSD'
                           } #Specs
             } #Optiplex

Our original hash table now has a nested one. With our new table we can get the RAM value by running the '$Optiplex.Specs.RAM' command. Feel free to follow along by the way.

But our computer information is still missing the Operating System details, let's add that.

$Optiplex = @{
                'Brand' = 'Dell'
                'Model' = 'Optiplex'
                'Specs' = @{
                                'RAM' = '4 GB'
                                'CPU' = 'Intel i5'
                                'USB Ports' = '3'
                                'CD/DVD Drive' = 'No'
                                'HDD' = '320 GB - SSD'
                                'OperatingSystem' = @{
                                                        'OSName' = 'Microsoft Windows 10'
                                                        'Version' = 'Pro'
                                                        'Build' = '19041'
                                                     } #Operating System
                           } #Specs
            } #Optiplex

The hash table containing the Operating System information is nested inside the nested 'Specs' hash table. Now we can get the OS Name by running '$Optiplex.Specs.OperatingSystem.OSName'. This could go on and on, although I would advise against going too deep when nesting stuff. If you do so, formatting becomes tricky and you may end up losing track of which "level" you are at.

Nesting the Nested Tables

OK, but Dell does have several desktop models and series, not just Optiplex. What if we want to add Vostro, Inspiron and XPS to our hash table?

$DellDesktopModels =
@{
#######################################################################################

'Optiplex' = @{
                'Brand' = 'Dell'
                'Model' = 'Optiplex'
                'Specs' = @{
                                'RAM' = '4 GB'
                                'CPU' = 'Intel i5'
                                'USB Ports' = '3'
                                'CD/DVD Drive' = 'No'
                                'HDD' = '320 GB - SSD'
                                'OperatingSystem' = @{
                                                        'OSName' = 'Microsoft Windows 10'
                                                        'Version' = 'Pro'
                                                        'Build' = '19041'
                                                     } #Operating System
                           } #Specs
              } #Optiplex


#######################################################################################
    
'Vostro' = @{
                'Brand' = 'Dell'
                'Model' = 'Vostro'
                'Specs' = @{
                                'RAM' = '8 GB'
                                'CPU' = 'Intel i7'
                                'USB Ports' = '4'
                                'CD/DVD Drive' = 'Yes'
                                'HDD' = '500 GB - SSD'
                                'OperatingSystem' = @{
                                                        'OSName' = 'Microsoft Windows 10'
                                                        'Version' = 'Enterprise'
                                                        'Build' = '19042'
                                                     } #Operating System
                           } #Specs

            } #Vostro


#######################################################################################    

'Inspiron' = @{
                'Brand' = 'Dell'
                'Model' = 'Inspiron'
                'Specs' = @{
                                'RAM' = '4 GB'
                                'CPU' = 'Intel i5'
                                'USB Ports' = '3'
                                'CD/DVD Drive' = 'No'
                                'HDD' = '1 TB - SATA'
                                'OperatingSystem' = @{
                                                        'OSName' = 'Microsoft Windows 10'
                                                        'Version' = 'Pro'
                                                        'Build' = '19041'
                                                     } #Operating System
                           } #Specs
              } #Inspiron


#######################################################################################

'XPS' = @{
            'Brand' = 'Dell'
            'Model' = 'XPS'
            'Specs' = @{
                            'RAM' = '4 GB'
                            'CPU' = 'Intel i5'
                            'USB Ports' = '6'
                            'CD/DVD Drive' = 'Yes'
                            'HDD' = '500 GB - SATA'
                            'OperatingSystem' = @{
                                                    'OSName' = 'Microsoft Windows 10'
                                                    'Version' = 'Home'
                                                    'Build' = '18363'
                                                 } #Operating System
                       } #Specs
         } #XPS

#######################################################################################

} # Main hash table

This looks more interesting now, but perhaps also a little different from what we had in the previous example. Here is a summary of the changes:

  1. We added a main hash table to wrap the original $Optiplex table.
  2. This new table is now a variable with the name $DellDesktopModels.
  3. The Optiplex table is now a key of the main $DellDesktopModels table. It is no longer a variable.
  4. Finally, we added a table for three additional types of Dell desktop computers: Vostro, Inspiron and XPS. These are also keys of the main table.
  5. Each computer type table is separated using a sequence of '#' characters to improve the code formatting readability.

Arrays

At this point, we can safely say we understand hash tables and nested hash tables. So, as a bonus, we are now going to add arrays to the mix. Arrays are very well known and commonly used in PowerShell but let's quickly review them.

Arrays are a collection of objects. They contain values (0 or more) within parentheses and are preceded by a '@' sign. The values inside an array are separated by commas or new lines. Each value inside an array have an index. The first index of an array is 0.

So, we can have an empty array: @()

Or an array with one or more items: @('one', 'two', 'three').

When an array is assigned to a variable we can use the indexes to reference any of its items:

PS C:\> $MyArr = @('one', 'two', 'three')
PS C:\> $MyArr[1]
two

Dynamic Values in Hash Tables

Now that we also understand arrays, let's use a few of them to add some values to the $DellDesktopModels table. Since we can manipulate or change the arrays, the values inside the hash tables that are populated from the arrays are no longer static, but dynamic instead.

$OSVersion = @(
    'Enterprise'
    'Pro'
    'Home'
)

$RAM = @(
    8
    4
)

$CPU = @(
    'Intel i7'
    'Intel i5'
)


$DellDesktopModels =
@{
#######################################################################################

'Optiplex' = @{
                'Brand' = 'Dell'
                'Model' = 'Optiplex'
                'Specs' = @{
                                'RAM' = "$($RAM[1]) GB"
                                'CPU' = $CPU[1]
                                'USB Ports' = '3'
                                'CD/DVD Drive' = 'No'
                                'HDD' = '320 GB - SSD'
                                'OperatingSystem' = @{
                                                        'OSName' = 'Microsoft Windows 10'
                                                        'Version' = $OSVersion[1]
                                                        'Build' = '19041'
                                                     } #Operating System
                           } #Specs
              } #Optiplex


#######################################################################################
    
'Vostro' = @{
                'Brand' = 'Dell'
                'Model' = 'Vostro'
                'Specs' = @{
                                'RAM' = "$($RAM[0]) GB"
                                'CPU' = $CPU[0]
                                'USB Ports' = '4'
                                'CD/DVD Drive' = 'Yes'
                                'HDD' = '500 GB - SSD'
                                'OperatingSystem' = @{
                                                        'OSName' = 'Microsoft Windows 10'
                                                        'Version' = $OSVersion[0]
                                                        'Build' = '19042'
                                                     } #Operating System
                           } #Specs

            } #Vostro


#######################################################################################    

'Inspiron' = @{
                'Brand' = 'Dell'
                'Model' = 'Inspiron'
                'Specs' = @{
                                'RAM' = "$($RAM[1]) GB"
                                'CPU' = $CPU[1]
                                'USB Ports' = '3'
                                'CD/DVD Drive' = 'No'
                                'HDD' = '1 TB - SATA'
                                'OperatingSystem' = @{
                                                        'OSName' = 'Microsoft Windows 10'
                                                        'Version' = $OSVersion[1]
                                                        'Build' = '19041'
                                                     } #Operating System
                           } #Specs
              } #Inspiron


#######################################################################################

'XPS' = @{
            'Brand' = 'Dell'
            'Model' = 'XPS'
            'Specs' = @{
                            'RAM' = "$($RAM[1]) GB"
                            'CPU' = $CPU[1]
                            'USB Ports' = '6'
                            'CD/DVD Drive' = 'Yes'
                            'HDD' = '500 GB - SATA'
                            'OperatingSystem' = @{
                                                    'OSName' = 'Microsoft Windows 10'
                                                    'Version' = $OSVersion[2]
                                                    'Build' = '18363'
                                                 } #Operating System
                       } #Specs
         } #XPS

#######################################################################################

} # Main hash table

We have added three arrays before the main hash table using variable names: $OSVersion, $RAM, $CPU. Each one of them has values that are valid for the specs categories associated to their respective variable name. For instance, RAM has the values of 4 and 8. As mentioned before, we can use the index to access the value of any array item, therefore, $RAM[1] refers to the second value (arrays start at 0) of the $RAM array, or '4'.

In the table the expression "$($RAM[1]) GB" is replaced by the value of $RAM[1], followed by 'GB'. Within the double quotes "$($RAM[1])" is expanded and its value becomes part of the string so we end up with '4 GB'. The same applies for '$OSVersion' and '$CPU'.

Let's see one last example of what we can do with the table above.

PS C:\> $DellDesktopModels.Vostro.Specs.OperatingSystem.Version
Enterprise

Note: We used arrays because it seems to make sense in the context of our example but there are many ways to dynamically assign values to a hash table; for instance, we could use arithmetic calculations when applicable.

Conclusion

Hash tables are useful to store information in the 'key - value' format (besides other uses already mentioned like splatting). We can nest them to add additional key value pairs inside the value of an existing table which helps build a structured scheme of information that can be used to hard-code small "databases" inside a script or function. We can also dynamically add values to a hash table, we used arrays in the examples but there are several other ways to do so.

Feel free to leave comments and questions below and thanks for reading this far.

The full and commented source code for this post is available in my GitHub repository.