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:
- Open Visual Studio Code or Notepad, paste the script below and save it as
inventory.ps1in a folder such asC:\Scripts. - Open PowerShell 7 (type
pwshin the Start menu) or Windows PowerShell, then runcd C:\Scripts. - Run
.\inventory.ps1. If Windows PowerShell blocks scripts, runSet-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSignedfirst. 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
