In a PowerShell script I needed to sort a hash table by byte value (not alphabetically, lowercase parameters will be listed after uppercase ones). An example for this requirement is the Amazon Product Advertising API.
Consider the following hashtable as an example:
$params = @{}
$params.Add("AssociateTag", "dummy")
$params.Add("AWSAccessKeyId", "AKIAIOSFODNN7EXAMPLE")
$params.Add("IdType", "0679722769")
$params.Add("Operation", "ItemLookup")If we use the Sort-Object to order the list (note that we need to use the GetEnumerator method):
$params.GetEnumerator() | Sort-Object NameWe will get the following result:
Name Value
---- -----
AssociateTag dummy
AWSAccessKeyId AKIAIOSFODNN7EXAMPLE
IdType 0679722769
Operation ItemLookupIf you use the -CaseSensitive switch the resulting order will remain the same.
I came up with the following code (download link below) to sort the hashtable in the required order:
function Sort-Binary {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)][HashTable]$HashTable,
[Switch]$Descending
)
$keys = $HashTable.Keys | ForEach {$_}
for ($i = 0; $i -lt $keys.Count - 1; $i++) {
for ($j = $i + 1; $j -lt $keys.Count; $j++) {
if ($Descending) {$swap = [String]::CompareOrdinal($keys[$i], $keys[$j]) -lt 0} else {$swap = [String]::CompareOrdinal($keys[$i], $keys[$j]) -gt 0}
if ($swap) {$keys[$i], $keys[$j] = $keys[$j], $keys[$i]}
}
}
$list = New-Object System.Collections.Specialized.OrderedDictionary
$keys | ForEach { $list.Add($_, $HashTable[$_]) }
return $list
}Let's try that:
$params = @{}
$params.Add("AssociateTag", "dummy")
$params.Add("AWSAccessKeyId", "AKIAIOSFODNN7EXAMPLE")
$params.Add("IdType", "0679722769")
$params.Add("Operation", "ItemLookup")
$params | Sort-Binary
Name Value
---- -----
AWSAccessKeyId AKIAIOSFODNN7EXAMPLE
AssociateTag dummy
IdType 0679722769
Operation ItemLookup
Hi Remko,
After some digging in System.Text a.o., this does the same thing (add sortkey to output to see what is sorts on)
$params.GetEnumerator() | sort @{e={ $_ | Add-Member -MemberType 'NoteProperty' -Name 'SortKey' -Value ([System.BitConverter]::ToString( [system.Text.Encoding]::ASCII.GetBytes( $_.Name))) }}, SortKey
Hi Michel, I tried to get something working using a Sort Expression but I had not thought of this approach. Very clever, like this better than my approach actually.
There's a .NET class called SortedDictionary which does this work for you, if you prefer. You just need to tell it what kind of comparison to perform (in this case, the built-in StringComparer.Ordinal class):
$params = New-Object -TypeName 'System.Collections.Generic.SortedDictionary[string,object]' -ArgumentList ([System.StringComparer]::Ordinal)
$params.Add("AssociateTag", "dummy")
$params.Add("AWSAccessKeyId", "AKIAIOSFODNN7EXAMPLE")
$params.Add("IdType", "0679722769")
$params.Add("Operation", "ItemLookup")
$params
That's nice Dave, .NET FrameWork is full of hidden gems. Thanks for adding that!