ConvertTo-OrderedDictionary

Summary: Learn about creating ordered dictionaries in Windows PowerShell 3.0.

Hash tables are fabulous for storing data items that are associated with each other. The simple “key=value” format is easy to create and easy to search.

The only problem is that the order of elements in the hash table is arbitrary. If you need to have the items in a particular order, you need to sort them every time.

PS C:> $hash = @{a=1; b=2; c=3}

PS C:> $hash

Name                           Value

—-                           —–

c                              3

b                              2

a                              1

PS C: > $hash.GetEnumerator() | sort

Name                           Value

—-                           —–

a                              1

b                              2

c                              3

A new feature in Windows PowerShell 3.0 makes it easy to create ordered dictionaries, which are like hash tables that maintain the order of elements. To create an ordered dictionary, simply place the [ordered] attribute before the “@” symbol.

PS C:> $dictionary = [ordered]@{a=1; b=2; c=3}

PS C:> $dictionary 

Name                           Value

—-                           —–

a                              1

b                              2

c                              3

Be sure to place the [ordered] before the @ and not before the variable name. If you mess up, you get the “The ordered attribute can be specified only on a hash literal node” error message, which is very accurate, I’m sure, but isn’t easy to interpret without a computer science degree. Interpret it as, “You can’t put the [ordered] attribute on a variable. Put it right before the @.”

PS C:> [ordered]$dictionary = @{a=1;b=2;c=3}

At line:1 char:1

+ [ordered]$dictionary = @{a=1;b=2;c=3}

+ ~~~~~~~~~~~~~~~~~~~~

The ordered attribute can be specified only on a hash literal node.

    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException

    + FullyQualifiedErrorId : OrderedAttributeOnlyOnHashLiteralNode

And the [ordered] attribute works ONLY on hash tables. You can’t use it on other types of collections, such as arrays (comma-separated lists), because they don’t have the key-value pair format.

PS C:> $colors = [ordered](“red”, “green”, “blue”)

At line:1 char:11

+ $colors = [ordered](“red”, “green”, “blue”)

+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ordered attribute can be specified only on a hash literal node.

    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException

    + FullyQualifiedErrorId : OrderedAttributeOnlyOnHashLiteralNode

Ordered dictionaries are instances of the System.Collections.Specialized.OrderedDictionary class. In addition to many hash table-like properties and methods, you get a few goodies that are available because of the preserved order, such as indexing.

You can index into items in an ordered dictionary. The following command gets the value of the item at index 1. (Remember that the index starts at zero.)

PS C:> $dictionary

Name                           Value

—-                           —–

a                              1

b                              2

c                              3

PS C:> $dictionary[1]

2

You can insert key-value pairs at a particular index position. The syntax of the Insert method is:

Insert(<index>, <key>, <value>)

PS C:> $dictionary.Insert(1, “d”, 2.5)

PS C:> $dictionary

Name                           Value

—-                           —–

a                              1

d                              2.5

b                              2

c                              3

You can remove key-value pairs at a particular index position. The syntax of the RemoveAt method is:

RemoveAt(<index>)

Name                           Value

—-                           —–

a                              1

d                              2.5

b                              2

c                              3

PS C:> $dictionary.RemoveAt(2)

PS C:> $dictionary 

Name                           Value

—-                           —–

a                              1

d                              2.5

c                              3

To perform operations on each element in an ordered dictionary, you can use a ForEach loop. That’s what I usually do.

PS C:> foreach ($value in $dictionary.values){$sum += $value}

PS C:> $sum

6.5

But because it’s indexed, you can also get values by using a traditional For loop.

PS C:> for ($i = 0; $i -lt $dictionary.count; $i++)

>> {$sum += $dictionary[$i]}

PS C:> $sum

6.5

All terrific stuff.  But if you forget about ordered dictionaries and create a standard hash table, you can’t use the [ordered] attribute to cast or convert a hash table to an ordered dictionary. If you do…

PS C:> $newDict = [ordered]$hash

At line:1 char:12

+ $newDict = [ordered]$hash

+            ~~~~~~~~~~~~~~

The ordered attribute can be specified only on a hash literal node.

    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException

    + FullyQualifiedErrorId : OrderedAttributeOnlyOnHashLiteralNode

So, I wrote a little script that converts a hash table to an ordered dictionary. Okay, it doesn’t really convert anything. It creates a new ordered dictionary, sorts the keys in the hash table alphanumerically, adds the sorted key-value pairs in the hash table to the ordered dictionary, and returns the ordered dictionary.

Here’s how you use it:

PS C:>$hash = @{a=1;b=2;c=3}

#Oops! I meant to create a dictionary

PS C:>$hash = .ConvertTo-OrderedDictionary.ps1 -Hash $hash

PS C:> $hash | Get-Member

   TypeName: System.Collections.Specialized.OrderedDictionary

PS C:> $hash

Name                           Value

—-                           —–

a                              1

b                              2

c                              3

PS C:> $hash.Insert(1, “ItWorks!”, [System.Math]::pi)

PS C:> $hash

Name                           Value

—-                           —–

a                              1

ItWorks!                       3.14159265358979

b                              2

c                              3

Ed mentioned that people also need to convert arrays to ordered dictionaries, so I added a little feature that does the deed. The keys in the ordered dictionary are integers beginning with 0, like this:

$winter = “December”, “January”, “February”

$winter = .ConvertTo-OrderedDictionary.ps1 -Hash $winter

PS C:> $winter

Name                           Value

—-                           —–

0                              December

1                              January

2                              February

If you convert the script to a function, you can pipe hash tables to it. The following command uses the AsHashTable parameter of the Group-Object cmdlet to get a hash table of the Convert cmdlets in each module. The command passes the hash table to ConvertTo-OrderedDictionary, which returns an ordered dictionary.

PS C:> $d = Get-Command Convert* | Group-Object -Property ModuleName -AsHashTable | ConvertTo-OrderedDictionary

PS C:> $d

Name                           Value

—-                           —–

                               {ConvertTo-OrderedDictionary, convert.exe}

Microsoft.PowerShell.Manage… {Convert-Path}

Microsoft.PowerShell.Security  {ConvertFrom-SecureString, ConvertTo-SecureString}

Microsoft.PowerShell.Utility   {ConvertFrom-Csv, ConvertFrom-Json, ConvertFrom-StringData, ConvertTo-Csv…}

MSOnline                       {Convert-MsolDomainToFederated, Convert-MsolDomainToStandard, Convert-MsolFederatedUser}

MSOnlineExtended               {Convert-MsolFederatedUser}

TrustedPlatformModule          {ConvertTo-TpmOwnerAuth}

 

PS C:> $d.gettype().fullname

System.Collections.Specialized.OrderedDictionary

The entire script is available to download from the Script Center Repository: ConvertTo-OrderedDictionary. Have fun!

~June

Thanks, June!

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy