A Better ToString() Method for Hash Tables
- Details
- Written by June Blender
- Last Updated: 21 April 2016
- Created: 21 October 2014
- Hits: 13377
I didn’t make it to the PowerShell Summit 2014 in Europe this year, but I’ve been getting much of the benefit by watching the Summit presentations on YouTube. After watching Windows PowerShell MVP Bartek Bielawski’s excellent presentation, in which he reviews PSCustomObjects and dynamic type data, I was inspired to fix a shortcoming in the .NET framework that often frustrates me.
It’s easy to create a hash table in Windows PowerShell:
@{Name = "Fred"; ID = "3"}
And, beginning in Windows PowerShell 3.0, it’s easy to get a hash table string from a file and convert it to a hash table object. Use the Invoke-Expression cmdlet and the Raw parameter of the Get-Content cmdlet. (Some nice folks on Twitter have reported that this technique does not work on files with digital signatures.)
$result = Invoke-Expression (Get-Content –Raw –Path <filename>)
For example, the module manifest files in Windows PowerShell contain a hash table string. If you open them in Notepad, they look like this:
If you get the content of that hash table…
Get-Content (Get-Module –List BranchCache).Path
… you get a string.
But if you use Invoke-Expression and the Raw parameter of Get-Content, you get a hash table. And, you can get the values of keys in the manifest hash table, such as FormatsToProcess, either by using a dot to get the property or by using conventional hash table syntax.
PS C:\> $manifest = ` Invoke-Expression (Get-Content –Raw -Path (Get-Module –List BranchCache).Path) PS C:\> $manifest.FormatsToProcess BranchCache.format.ps1xmlPS C:\> $manifest['FormatsToProcess'] BranchCache.format.ps1xml
So, the only piece that was missing was saving a hash table as a string in a file. The obvious solution is the ToString() method of hash tables, but that just returns the type.
PS C:\> $manifest = ` Invoke-Expression (Get-Content –Raw -Path (Get-Module –List BranchCache).Path) PS C:\> $manifest.toString() System.Collections.Hashtable
I wrote a little function that converts any hash table to a string. There’s no magic here. It just builds a string that conforms to the hash table syntax for Windows PowerShell. The only tricky part was using escape characters to preserve quotation marks when the key includes a special character. (Hint: I used PowerRegEx in the SAPIEN Productivity Pack to find that “\s”.)
function Convert-HashToString { param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Hash ) $hashstr = "@{" $keys = $Hash.keys foreach ($key in $keys) { $v = $Hash[$key] if ($key -match "\s") { $hashstr += "`"$key`"" + "=" + "`"$v`"" + ";" } else { $hashstr += $key + "=" + "`"$v`"" + ";" } } $hashstr += "}" return $hashstr }
But, I don’t want to run a function or script. I want this code to run when I call the ToString() method of a hash table. So, I used the Update-TypeData trick that Bartek explains so nicely. (You can also read about it in the help topic for Update-TypeData, but Bartek is more fun to watch.)
This was easier than I thought. I used the Update-TypeData cmdlet. I set the value of the TypeName parameter to the full name of the hash table type, System.Collections.HashTable. It’s a script for a method, so I used the ScriptMethod member type. And, I want to override the current (not very helpful) ToString() method, so I set the MemberName parameter value to ToString.
The Value is the content of my little function. The only change I made was to replace the $hash parameter value with $this, which indicates the instances of the HashTable class.
Update-TypeData -TypeName System.Collections.HashTable ` -MemberType ScriptMethod ` -MemberName ToString ` -Value { $hashstr = "@{"; $keys = $this.keys; foreach ($key in $keys) { $v = $this[$key]; if ($key -match "\s") { $hashstr += "`"$key`"" + "=" + "`"$v`"" + ";" } else { $hashstr += $key + "=" + "`"$v`"" + ";" } }; $hashstr += "}"; return $hashstr }
Voila! When I ran it, I could use the new method to generate a hash table string.
PS C:\> $h = @{ Name = "JuneB"; Language = "PowerShell" } PS C:\> $h.Language PowerShell PS C:\> $h.ToString() @{ Name = "JuneB"; Language = "PowerShell"; }
Now, it’s easy to save the hash table string in a file.
PS C:\> $h.ToString() > .\hash.txt PS C:\> Get-Content .\hash.txt @{ Name = "JuneB"; Language = "PowerShell"; }
And, convert it back to a hash table.
PS C:\> $hashback = Invoke-Expression (Get-Content -Raw .\hash.txt) PS C:\> $hashback.language PowerShellPS C:\> $hashback.gettype().fullname System.Collections.Hashtable
The only glitch is that dynamic type data is specific to a session, so I saved the Update-TypeData command in my Windows PowerShell profile. Now, it’s available to me in every Windows PowerShell session that uses my profile.
The moral of the story is that it takes an hour to watch a PowerShell Summit presentation, but the inspiration might save you days of work.
June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at This email address is being protected from spambots. You need JavaScript enabled to view it. and follow her on Twitter at @juneb_get_help.
For licensed customers, use the forum associated with your product in our Product Support Forums for Registered Customers.
For users of trial versions, please post in our Former and Future Customers - Questions forum.