PowerShell Hash Tables

A hash table is an array of key/value pairs, along with accompanying functions for
readily accessing or modifying values by way of their corresponding keys.

Creating and Populating a Hash Table

Here is the PowerShell syntax for initializing an empty hash table object:

$myHashTable = @{}

We add elements to the hash table quite easily via several different mechanisms.

$key = "key 1"
$value = "value 1"
$myHashTable.add($key,$value)
$myHashTable.add("key 2", "value 2")

Here is what the resulting hash table looks like:

PS C:\> $myHashTable

Name             Value 
----             ----- 
key 2            value 2 
key 1            value 1

Alternatively, we could have populated the hash table at the time that we created it via the following syntax:

$myHashTable2 = @{
 100 = "One Hundred"
 1000 = "One Thousand"
 10000 = "Ten Thousand"
 }

It should be noted that order is not necessarily preserved in a hash table:

PS C:\> $myHashTable2

Name                  Value 
----                  ----- 
1000                  One Thousand 
10000                 Ten Thousand 
100                   One Hundred

There is, however, a syntax for constructing a hash table of type [ordered] if that is needed:

PS C:\> $myHashTable2 = [ordered]@{
 100 = "One Hundred"
 1000 = "One Thousand"
 10000 = "Ten Thousand"
 }

PS C:\> $myHashTable2

Name                 Value 
----                 ----- 
100                  One Hundred 
1000                 One Thousand 
10000                Ten Thousand

Accessing Hash Table Values

Accessing the values of hash table entries by means of the keys is pretty straightforward, and multiple syntaxes are provided for doing so.

PS C:\> $myHashTable["key 1"]
value 1

PS C:\> $myHashTable."key 2"
value 2

PS C:\> $myHashTable2.1000
One Thousand

I should note here, however, that the [] syntax changes behavior when the hash table is defined as an ordered hash table (as in the way in which I have redefined $myHashTable2 in these examples). In that case, the arguments in square brackets are passed as an array index rather than a key.

PS C:\> $myHashTable2[100]

PS C:\> $myHashTable2[0]
One Hundred

Modifying Hash Table Entries

blah

Silly Hash Table Tricks

Keep in mind that key names are simply strings, and since PowerShell strings by default use UTF-16 encoding, we are squarely in the world of Unicode, which can, in turn, include Emoji.

PS C:> $myHashTable = @{
"💩" = "Poo Emoji" 
"🎼" = "Treble Clef"
"⏰" = "Clock"
"😁" = "Laughing Emoji"
}

PS C:> $myHashTable."🎼"
Treble Clef

PS C:> $myHashTable."💩"
Poo Emoji

I don’t know how useful this particularly is, but it amuses me. It is somewhat reminiscent of the ability to use Unicode strings to define variable names in Apple’s programming language, Swift.

Here is a more practical example in which the hash table is used literally as a dictionary lookup table, translating Korean words (rendered in Hangul) to English.

PS C:\> $myHashTable = @{
 가다 = "to go"
 웃다 = "to come"
 먹다 = "to eat"
}

PS C:\> $myHashTable.가다
to go

Creating Calculated Properties

When using Select-Object, Format-List, or Format-Table, it is possible to use hash tables to dynamically rename attributes or to calculate synthetically-generated attributes on the fly.  For example, running the Get-ChildItem command to examine filesystem objects results in the permissions of each object being returned in an attribute called “Mode”.  We can use a hash table to rewrite this attribute name on the fly as something more descriptive.

PS C:\> Get-ChildItem | where {$_.Name -like "Users"} | select Mode,Name | ft -AutoSize

Mode   Name 
----   ---- 
d-r-- Users

PS C:\> $property = @{
 name = "Permissions"
 expression = {$_.Mode}
 }

PS C:\> Get-ChildItem | where {$_.Name -like "Users"} | select $property,Name | ft -AutoSize

Permissions  Name 
-----------  ---- 
d-r--       Users

Of course, this particular sort of application of hash tables in PowerShell is more commonly seen with the hash table defined in-line.  However, that approach somewhat degrades readability of the script.

PS C:\> Get-ChildItem | where {$_.Name -like "Users"} | select @{name = "Permissions"; expression = {$_.Mode}},Name | ft -AutoSize

Permissions  Name 
-----------  ---- 
d-r--       Users

We’ve shown how to rename a property on the fly, but how about dynamically generating calculated attributes?  For example, when we run Get-ChildItem on files, the size is returned, in bytes, in an attribute called “Length”.

PS C:\Windows\System32> Get-ChildItem win*.exe | ft Name,Length -AutoSize

Name          Length
----          ------
wininit.exe   129024
winload.exe   634272
winlogon.exe  455680
winresume.exe 546656
winrs.exe      46080
winrshost.exe  23040
winver.exe     80384

What if we instead want the size to be shown in kilobytes, with the name of the property modified appropriately to reflect this?

PS C:\Windows\System32> $size = @{
name = "Size (KB)"
expression = {($_.Length)/1KB}
}

PS C:\Windows\System32> Get-ChildItem win*.exe | ft Name,$size -AutoSize

Name          Size (KB)
----          ---------
wininit.exe         126
winload.exe   619.40625
winlogon.exe        445
winresume.exe 533.84375
winrs.exe            45
winrshost.exe      22.5
winver.exe         78.5

Splatting PowerShell Parameters With Hash Tables

It is possible to pass collections of parameters to PowerShell cmdlets in the form of hash tables.  This feature, introduced in PowerShell v2, is called “splatting.”

PS C:> $myColors = @{
ForegroundColor = "Cyan"
BackgroundColor = "Black"
}

PS C:> Write-Host "Hello" @myColors
Hello

You can mix in “splatted” parameters with parameters passed via normal means, as well as passing mulitple hash tables, all provided that none the parameters passed conflict.

PS C:> $sep = @{
 Separator = "<---->"
 }

PS C:> Write-Host "Thing 1","Thing 2","Thing 3" @sep @myColors -NoNewline ; Write-Host "!"
Thing 1<---->Thing 2<---->Thing 3!

Hash Table Example: Mapping O365 SKUs to License Names

blah

Note that this list is by no means complete but rather reflects what I see in the tenant that I administer.  It even includes SKUs that have since been deprecated and replaced by other SKUs.  This hash table includes both SKUs and services, and the entries are presented in no particular order.

$names = @{
 "STANDARDWOFFPACK_STUDENT" = "Office 365 A1 for students"
 "STANDARDWOFFPACK_IW_STUDENT"="Office 365 A1 Plus for students"
 "PROJECTONLINE_PLAN_1_FACULTY"="Project Online Premium without Project Client for faculty"
 "EXCHANGEENTERPRISE_FACULTY"="Exchange Online (Plan 2) for faculty"
 "PROJECTONLINE_PLAN_2_FACULTY"="Project Online with Project Pro for Office 365 for Faculty"
 "STANDARDWOFFPACK"="Microsoft Office 365 Plan E2"
 "OFFICESUBSCRIPTION_FACULTY"="Office 365 ProPlus for faculty"
 "OFFICESUBSCRIPTION_STUDENT"="Office 365 ProPlus for students"
 "STANDARDWOFFPACK_FACULTY"="Office 365 A1 for faculty"
 "ATP_ENTERPRISE_FACULTY" = "Exchange Online Advanced Threat Protection for Faculty"
 "RMS_S_ENTERPRISE"="Azure Rights Management"
 "OFFICE_FORMS_PLAN_2"="Microsoft Forms (Plan 2)"
 "PROJECTWORKMANAGEMENT"="Microsoft Planner"
 "SWAY"="Sway"
 "INTUNE_O365"="Mobile Device Management for Office 365"
 "YAMMER_EDU"="Yammer for Academic"
 "SHAREPOINTWAC_EDU" = "Office Online for Education"
 "MCOSTANDARD" = "Skype for Business Online (Plan 2)"
 "SHAREPOINTSTANDARD_EDU" = "SharePoint Plan 1 for EDU"
 "EXCHANGE_S_STANDARD" = "Exchange Online (Plan 1)"
 "OFFICESUBSCRIPTION" = "Office 365 ProPlus"
 "EXCHANGE_S_FOUNDATION" = "Exchange Foundation (?)"
 "SHAREPOINT_PROJECT_EDU" = "Project Online Service for Education"
 "SHAREPOINTENTERPRISE_EDU" = "SharePoint Plan 2 for EDU"
 "EXCHANGE_S_ENTERPRISE" = "Exchange Online (Plan 2)"
 "PROJECT_CLIENT_SUBSCRIPTION" = "Project Online Desktop Client"
 "ONEDRIVESTANDARD" = "OneDrive for Business (Plan 1)"
 "FLOW_O365_P2" = "Flow for Office 365"
 "POWERAPPS_O365_P2" = "PowerApps for Office 365"
 "Deskless" = "Microsoft StaffHub"
 "TEAMS1" = "Microsoft Teams"
 "STREAM_O365_E3" = "Microsoft Stream for O365 E3 SKU"
 "EMS" = "Enterprise Mobility + Security E3"
 "RMS_S_PREMIUM" = "Azure Information Protection Plan 1"
 "INTUNE_A" = "Intune A Direct"
 "AAD_PREMIUM" = "Azure Active Directory Premium Plan 1"
 "MFA_PREMIUM" = "Azure Multi-Factor Authentication"
 "POWER_BI_STANDARD_FACULTY" = "Power BI (free) for faculty"
 "BI_AZURE_P0" = "Power BI (free)"
 "ATP_ENTERPRISE" = "Advanced Threat Protection"
 "MCOMEETADV_FACULTY" = "Skype for Business PSTN Conferencing for faculty"
 "MCOMEETADV" = "Skype for Business PSTN Conferencing"
 "SCHOOL_DATA_SYNC_P1" = "School Data Sync (Plan 1)"
 "FORMS_PLAN_E1" = "Microsoft Forms (Plan E1)"
 "PROJECTPREMIUM_FACULTY" = "Project Online Premium for faculty"
 "PROJECTESSENTIALS_FACULTY" = "Project Online Essentials for faculty"
 "PROJECT_ESSENTIALS" = "Project Online Essentials"
 "BPOS_S_TODO_2"="To-Do (Plan 2)"
 "AAD_BASIC_EDU"="Azure Active Directory Basic for Edu"
 "ADALLOM_S_DISCOVERY" = "Cloud App Security Discovery"
 "POWER_BI_PRO_FACULTY" = "Power BI Pro for faculty"
 "PROJECTPROFESSIONAL_FACULTY" = "Project Online Professional for faculty"
 }

Let’s say that we want to use this hash table to show the friendly names for all of the SKUs returned by Get-MsolAccountSku:

PS C:> $skus=Get-MsolAccountSku

PS C:> foreach ($sku in $skus){
 $skupart = $sku.SkuPartNumber
 Write-Host ("{0,-28} {1,-40}" -f $skupart,$names.Item($skupart)) 
}
PROJECTESSENTIALS_FACULTY    Project Online Essentials for faculty 
MCOMEETADV_FACULTY           Skype for Business PSTN Conferencing for faculty
ATP_ENTERPRISE_FACULTY       Exchange Online Advanced Threat Protection for Faculty
POWER_BI_STANDARD_FACULTY    Power BI (free) for faculty 
STANDARDWOFFPACK_STUDENT     Office 365 A1 for students 
POWER_BI_PRO_FACULTY         Power BI Pro for faculty 
STANDARDWOFFPACK_IW_STUDENT  Office 365 A1 Plus for students 
EXCHANGEENTERPRISE_FACULTY   Exchange Online (Plan 2) for faculty 
PROJECTPROFESSIONAL_FACULTY  Project Online Professional for faculty 
EMS                          Enterprise Mobility + Security E3 
OFFICESUBSCRIPTION_FACULTY   Office 365 ProPlus for faculty 
OFFICESUBSCRIPTION_STUDENT   Office 365 ProPlus for students 
STANDARDWOFFPACK_FACULTY     Office 365 A1 for faculty 
PROJECTPREMIUM_FACULTY       Project Online Premium for faculty

Suppose that we no want to take a look at all of the services under a specific SKU, say the one for EMS:

PS C:\Working\Scripts\glenmartin\lice> foreach ($service in $skus[9].ServiceStatus) {
 $ServName = $names.Item($service.ServicePlan.ServiceName)
 Write-Host ("{0} {1,-25} {2,-40} {3,-15}" -f "`t",$service.ServicePlan.ServiceName,$ServName,$service.ProvisioningStatus) 
}
 EXCHANGE_S_FOUNDATION     Exchange Foundation (?)                  Success 
 ADALLOM_S_DISCOVERY       Cloud App Security Discovery             Success 
 RMS_S_PREMIUM             Azure Information Protection Plan 1      Success 
 INTUNE_A                  Intune A Direct                          Success 
 RMS_S_ENTERPRISE          Azure Rights Management                  Success 
 AAD_PREMIUM               Azure Active Directory Premium Plan 1    Success 
 MFA_PREMIUM Azure         Multi-Factor Authentication              Success

(Note that I have a question mark in the Exchange Foundation service name since Microsoft does not document this one at all, nor does this service get listed in the web interface.)

For More Information

Leave a Reply