PowerShell Hashtable (Dictionary): Create, Add, Loop and Sort

Think of a hashtable in PowerShell as a lookup list: each value sits under a key, and you get it back by that key instead of by position. Create one with @{ Key = 'Value' } and read it with $table['Key']:

$capitals = @{
    Texas    = 'Austin'
    Colorado = 'Denver'
    Florida  = 'Tallahassee'
}

"Capital of Texas: $($capitals['Texas'])"
"Same with dot notation: $($capitals.Colorado)"
"Number of entries: $($capitals.Count)"

Output:

Capital of Texas: Austin
Same with dot notation: Denver
Number of entries: 3

A hashtable is PowerShell’s built-in dictionary, so you’ll also see it called a dictionary or an associative array. They’re the same thing here.

In this tutorial, I’ll show you how to create a hashtable in PowerShell, add and remove keys, loop through it, sort it, nest it, copy it, convert it to JSON, and pick a typed .NET Dictionary when you need one.

All the code below ran in PowerShell 7.6 and Windows PowerShell 5.1. Where the two give different results, you’ll see both outputs.

How do you create a hashtable in PowerShell?

Start with @, wrap the pairs in braces, and put = between each key and its value. Separate pairs with a semicolon or a new line, and quote any key that contains a space:

$empty    = @{}
$employee = @{ Name = 'Maria Lopez'; City = 'Austin'; Age = 34 }
$contact  = @{ 'Zip Code' = '78701'; 'Phone Number' = '512-555-0142' }

"Empty table count: $($empty.Count)"
"Type: $($employee.GetType().FullName)"
"Zip Code: $($contact['Zip Code'])"
"Phone: $($contact.'Phone Number')"

Output:

Empty table count: 0
Type: System.Collections.Hashtable
Zip Code: 78701
Phone: 512-555-0142

Every @{} is a System.Collections.Hashtable. Values can be any type, and the PowerShell data types guide explains how numbers, strings and dates behave as values.

Read a quoted key with brackets, or with dot notation and quotes, like $contact.'Phone Number'. I use brackets whenever the key comes from a variable.

Add, update and remove entries

Assigning to a key adds it if it’s new and updates it if it exists. Add() only adds, and fails if the key is already there:

$stock = @{ Laptops = 12; Monitors = 30 }

$stock['Keyboards'] = 45      # adds a new key
$stock['Laptops']   = 10      # updates an existing key
$stock.Remove('Monitors')

try { $stock.Add('Laptops', 99) } catch { 'Add() failed. Laptops is already a key' }
$stock.GetEnumerator() | Sort-Object -Property Name | ForEach-Object { '{0} = {1}' -f $_.Name, $_.Value }

Output:

Add() failed. Laptops is already a key
Keyboards = 45
Laptops = 10

I use the bracket form almost everywhere, since it never fails. Remove() is also safe when the key doesn’t exist. Unlike appending to an array, adding a key never copies the whole collection.

Missing keys and case

Reading a key that doesn’t exist returns $null instead of an error, so check with ContainsKey when it matters. Keys ignore case by default:

$capitals = @{ Texas = 'Austin' }

"Missing key returns: [$($capitals['Ohio'])]"
"ContainsKey('Ohio'):  $($capitals.ContainsKey('Ohio'))"
"Keys ignore case:     $($capitals['TEXAS'])"

Output:

Missing key returns: []
ContainsKey('Ohio'):  False
Keys ignore case:     Austin

Number keys and text keys are different

The key 101 and the key ‘101’ aren’t the same. This bites when the lookup value comes from a CSV file or Read-Host, which are always text:

$rooms = @{ 101 = 'Sales'; 102 = 'Finance' }

"Number key 101:  [$($rooms[101])]"
"Text key '101':  [$($rooms['101'])]"
"Cast first:      [$($rooms[[int]'101'])]"

Output:

Number key 101:  [Sales]
Text key '101':  []
Cast first:      [Sales]

Build a server inventory hashtable step by step

Here’s a small script that puts the pieces together. It starts empty, adds servers in a loop, updates one, adds another with +=, then reports. To try it yourself:

  1. Open Visual Studio Code or Notepad, paste the script below and save it as inventory.ps1 in a folder such as C:\Scripts.
  2. Open PowerShell 7 (type pwsh in the Start menu) or Windows PowerShell, then run cd C:\Scripts.
  3. Run .\inventory.ps1. If Windows PowerShell blocks scripts, run Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned first. It only affects that window.
$inventory = @{}

foreach ($server in 'WEB01', 'WEB02', 'SQL01') {
    $inventory[$server] = 'Online'
}
$inventory['SQL01'] = 'Patching'
$inventory += @{ CACHE01 = 'Offline' }

"SQL01 status: $($inventory['SQL01'])"
"Servers online: $(($inventory.GetEnumerator() | Where-Object Value -eq 'Online').Count)"
$inventory.GetEnumerator() | Sort-Object -Property Name |
    ForEach-Object { '{0,-8} {1}' -f $_.Name, $_.Value }

Output:

SQL01 status: Patching
Servers online: 2
CACHE01  Offline
SQL01    Patching
WEB01    Online
WEB02    Online