{"id":8019,"date":"2014-10-21T06:00:00","date_gmt":"2014-10-21T13:00:00","guid":{"rendered":"http:\/\/www.sapien.com\/blog\/?p=8019"},"modified":"2014-10-17T09:42:37","modified_gmt":"2014-10-17T16:42:37","slug":"a-better-tostring-method-for-hash-tables","status":"publish","type":"post","link":"https:\/\/dev.sapien.com\/blog\/2014\/10\/21\/a-better-tostring-method-for-hash-tables\/","title":{"rendered":"A Better ToString() Method for Hash Tables"},"content":{"rendered":"<p>I didn\u2019t make it to the PowerShell Summit 2014 in Europe this year, but I\u2019ve been getting much of the benefit by watching the <a href=\"http:\/\/tinyurl.com\/PSHSummitE14\">Summit presentations on YouTube<\/a>. After watching Windows PowerShell MVP <a href=\"https:\/\/www.youtube.com\/watch?v=ae8uBxIwhWE&amp;index=6&amp;list=PLfeA8kIs7Coehjg9cB6foPjBojLHYQGb_\">Bartek Bielawski\u2019s excellent presentation<\/a>, in which he reviews PSCustomObjects and dynamic type data, I was inspired to fix a shortcoming in the .NET framework that often frustrates me.<\/p>\n<p>It\u2019s easy to create a hash table in Windows PowerShell:<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\">@{Name <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"Fred\"<\/span><span style=\"color: #000000;\">; ID <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"3\"<\/span><span style=\"color: #000000;\">}<\/span><\/pre>\n<\/blockquote>\n<p>&nbsp;<\/p>\n<p>And, beginning in Windows PowerShell 3.0, it\u2019s 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.)<\/p>\n<blockquote>\n<pre><span style=\"color: #8b0000;\">$result<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"font-weight: bold; color: #0000ff;\">Invoke-Expression<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"font-weight: bold; color: #0000ff;\">Get-Content<\/span><span style=\"color: #000000;\"> \u2013Raw \u2013Path &lt;filename&gt;)<\/span><\/pre>\n<\/blockquote>\n<p>&nbsp;<\/p>\n<p>For example, the module manifest files in Windows PowerShell contain a hash table string. If you open them in Notepad, they look like this:<\/p>\n<blockquote><p><a href=\"http:\/\/www.sapien.com\/blog\/wp-content\/uploads\/2014\/10\/image11.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"image\" src=\"http:\/\/www.sapien.com\/blog\/wp-content\/uploads\/2014\/10\/image_thumb9.png\" alt=\"image\" width=\"423\" height=\"315\" border=\"0\" \/><\/a><\/p><\/blockquote>\n<p>If you get the content of that hash table&#8230;<\/p>\n<blockquote>\n<pre><span style=\"font-weight: bold; color: #0000ff;\">Get-Content<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"font-weight: bold; color: #0000ff;\">Get-Module<\/span><span style=\"color: #000000;\"> \u2013List BranchCache).Path<\/span><\/pre>\n<\/blockquote>\n<p>&#8230; you get a string.<\/p>\n<p>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.<\/p>\n<blockquote>\n<pre><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$manifest<\/span> <span style=\"color: #0000ff;\">=<\/span> `\r\n    <span style=\"font-weight: bold; color: #0000ff;\">Invoke-Expression<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"font-weight: bold; color: #0000ff;\">Get-Content<\/span><span style=\"color: #000000;\"> \u2013Raw <\/span><span style=\"color: #3399ff;\">-Path<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"font-weight: bold; color: #0000ff;\">Get-Module<\/span><span style=\"color: #000000;\"> \u2013List BranchCache).Path)\r\n<\/span><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$manifest<\/span><span style=\"color: #000000;\">.FormatsToProcess\r\n<\/span><span style=\"color: #696969;\">BranchCache.format.ps1xml<\/span><\/pre>\n<pre><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$manifest<\/span><span style=\"color: #000000;\">[<\/span><span style=\"color: #ff0000;\">'FormatsToProcess'<\/span><span style=\"color: #000000;\">]\r\n<\/span><span style=\"color: #696969;\">BranchCache.format.ps1xml<\/span><\/pre>\n<\/blockquote>\n<p>&nbsp;<\/p>\n<p>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.<\/p>\n<blockquote>\n<pre><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$manifest<\/span> <span style=\"color: #0000ff;\">=<\/span> `\r\n    <span style=\"font-weight: bold; color: #0000ff;\">Invoke-Expression<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"font-weight: bold; color: #0000ff;\">Get-Content<\/span><span style=\"color: #000000;\"> \u2013Raw <\/span><span style=\"color: #3399ff;\">-Path<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"font-weight: bold; color: #0000ff;\">Get-Module<\/span><span style=\"color: #000000;\"> \u2013List BranchCache).Path)\r\n<\/span><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$manifest<\/span><span style=\"color: #000000;\">.toString()\r\n<\/span><span style=\"color: #696969;\">System.Collections.Hashtable<\/span><\/pre>\n<\/blockquote>\n<p>&nbsp;<\/p>\n<p>I wrote a little function that converts any hash table to a string. There\u2019s 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 <a href=\"http:\/\/www.sapien.com\/software\/productivity_pack\">PowerRegEx in the SAPIEN Productivity Pack<\/a> to find that \u201c\\s\u201d.)<\/p>\n<pre>    <span style=\"color: #0000ff;\">function<\/span> <span style=\"color: #008080;\">Convert-HashToString<\/span><span style=\"color: #000000;\">\r\n    {\r\n        <\/span><span style=\"color: #0000ff;\">param<\/span><span style=\"color: #000000;\">\r\n        (\r\n            [<\/span><span style=\"color: #4682b4;\">Parameter<\/span><span style=\"color: #000000;\">(Mandatory <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #8b0000;\">$true<\/span><span style=\"color: #000000;\">)]\r\n            [<\/span><span style=\"color: #0000cd;\">System.Collections.Hashtable<\/span><span style=\"color: #000000;\">]\r\n            <\/span><span style=\"color: #8b0000;\">$Hash<\/span><span style=\"color: #000000;\">\r\n        )\r\n        <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"@{\"<\/span>\r\n        <span style=\"color: #8b0000;\">$keys<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #8b0000;\">$Hash<\/span><span style=\"color: #000000;\">.keys\r\n        <\/span><span style=\"color: #0000ff;\">foreach<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"color: #8b0000;\">$key<\/span> <span style=\"color: #0000ff;\">in<\/span> <span style=\"color: #8b0000;\">$keys<\/span><span style=\"color: #000000;\">)\r\n        {\r\n            <\/span><span style=\"color: #8b0000;\">$v<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #8b0000;\">$Hash<\/span><span style=\"color: #000000;\">[<\/span><span style=\"color: #8b0000;\">$key<\/span><span style=\"color: #000000;\">]\r\n            <\/span><span style=\"color: #0000ff;\">if<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"color: #8b0000;\">$key<\/span> <span style=\"color: #0000ff;\">-match<\/span> <span style=\"color: #ff0000;\">\"\\s\"<\/span><span style=\"color: #000000;\">)\r\n            {\r\n                <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">+=<\/span> <span style=\"color: #ff0000;\">\"`\"$key`\"\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"=\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"`\"$v`\"\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\";\"<\/span><span style=\"color: #000000;\">\r\n            }\r\n            <\/span><span style=\"color: #0000ff;\">else<\/span><span style=\"color: #000000;\">\r\n            {\r\n                <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">+=<\/span> <span style=\"color: #8b0000;\">$key<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"=\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"`\"$v`\"\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\";\"<\/span><span style=\"color: #000000;\">\r\n            }\r\n        }\r\n        <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">+=<\/span> <span style=\"color: #ff0000;\">\"}\"<\/span>\r\n        <span style=\"color: #0000ff;\">return<\/span> <span style=\"color: #8b0000;\">$hashstr<\/span><span style=\"color: #000000;\">\r\n    }<\/span><\/pre>\n<p>&nbsp;<\/p>\n<p>But, I don\u2019t 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 <a href=\"http:\/\/technet.microsoft.com\/library\/hh849908.aspx\">help topic for Update-TypeData<\/a>, but Bartek is more fun to watch.)<\/p>\n<p>This was easier than I thought. I used the Update-TypeData cmdlet. I set the value of the TypeName parameter\u00a0to the full name of the hash table type, System.Collections.HashTable. It\u2019s 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\u00a0to ToString.<\/p>\n<p>The Value is the content of my little function. The only change I made was to replace the <strong>$hash<\/strong> parameter value with <strong>$this<\/strong>, which indicates the instances of the HashTable class.<\/p>\n<pre>    <span style=\"font-weight: bold; color: #0000ff;\">Update-TypeData<\/span> <span style=\"color: #3399ff;\">-TypeName<\/span><span style=\"color: #000000;\"> System.Collections.HashTable `\r\n    <\/span><span style=\"color: #3399ff;\">-MemberType<\/span><span style=\"color: #000000;\"> ScriptMethod `\r\n    <\/span><span style=\"color: #3399ff;\">-MemberName<\/span><span style=\"color: #000000;\"> ToString `\r\n    <\/span><span style=\"color: #3399ff;\">-Value<\/span><span style=\"color: #000000;\"> { <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"@{\"<\/span><span style=\"color: #000000;\">; <\/span><span style=\"color: #8b0000;\">$keys<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #8b0000;\">$this<\/span><span style=\"color: #000000;\">.keys; <\/span><span style=\"color: #0000ff;\">foreach<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"color: #8b0000;\">$key<\/span> <span style=\"color: #0000ff;\">in<\/span> <span style=\"color: #8b0000;\">$keys<\/span><span style=\"color: #000000;\">) { <\/span><span style=\"color: #8b0000;\">$v<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #8b0000;\">$this<\/span><span style=\"color: #000000;\">[<\/span><span style=\"color: #8b0000;\">$key<\/span><span style=\"color: #000000;\">]; \r\n             <\/span><span style=\"color: #0000ff;\">if<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"color: #8b0000;\">$key<\/span> <span style=\"color: #0000ff;\">-match<\/span> <span style=\"color: #ff0000;\">\"\\s\"<\/span><span style=\"color: #000000;\">) { <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">+=<\/span> <span style=\"color: #ff0000;\">\"`\"$key`\"\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"=\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"`\"$v`\"\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\";\"<\/span><span style=\"color: #000000;\"> }\r\n             <\/span><span style=\"color: #0000ff;\">else<\/span><span style=\"color: #000000;\"> { <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">+=<\/span> <span style=\"color: #8b0000;\">$key<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"=\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\"`\"$v`\"\"<\/span> <span style=\"color: #0000ff;\">+<\/span> <span style=\"color: #ff0000;\">\";\"<\/span><span style=\"color: #000000;\"> } }; <\/span><span style=\"color: #8b0000;\">$hashstr<\/span> <span style=\"color: #0000ff;\">+=<\/span> <span style=\"color: #ff0000;\">\"}\"<\/span><span style=\"color: #000000;\">;\r\n             <\/span><span style=\"color: #0000ff;\">return<\/span> <span style=\"color: #8b0000;\">$hashstr<\/span><span style=\"color: #000000;\"> }<\/span><\/pre>\n<p>&nbsp;<\/p>\n<p>Voila! When I ran it, I could use the new method to generate a hash table string.<\/p>\n<pre>    <span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$h<\/span><span style=\"color: #000000;\"> = @{ Name <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"JuneB\"<\/span><span style=\"color: #000000;\">; Language <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"PowerShell\"<\/span><span style=\"color: #000000;\"> }\r\n\r\n    <\/span><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$h<\/span><span style=\"color: #000000;\">.Language\r\n    <\/span><span style=\"font-weight: bold; color: #0000ff;\">PowerShell<\/span>\r\n\r\n    <span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$h<\/span><span style=\"color: #000000;\">.ToString()\r\n    @{ Name <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"JuneB\"<\/span><span style=\"color: #000000;\">; Language <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"PowerShell\"<\/span><span style=\"color: #000000;\">; }<\/span><\/pre>\n<p>&nbsp;<\/p>\n<p>Now, it\u2019s easy to save the hash table string in a file.<\/p>\n<pre>    <span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$h<\/span><span style=\"color: #000000;\">.ToString() &gt; .\\hash.txt\r\n\r\n    <\/span><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #00008b;\">Get-Content<\/span><span style=\"color: #000000;\"> .\\hash.txt\r\n    @{ Name <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"JuneB\"<\/span><span style=\"color: #000000;\">; Language <\/span><span style=\"color: #0000ff;\">=<\/span> <span style=\"color: #ff0000;\">\"PowerShell\"<\/span><span style=\"color: #000000;\">; }<\/span><\/pre>\n<p>&nbsp;<\/p>\n<p>And, convert it back to a hash table.<\/p>\n<blockquote>\n<pre><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$hashback<\/span> <span style=\"color: #0000ff;\">=<\/span> <span style=\"font-weight: bold; color: #0000ff;\">Invoke-Expression<\/span><span style=\"color: #000000;\"> (<\/span><span style=\"font-weight: bold; color: #0000ff;\">Get-Content<\/span> <span style=\"color: #3399ff;\">-Raw<\/span><span style=\"color: #000000;\"> .\\hash.txt)\r\n\r\n<\/span><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$hashback<\/span><span style=\"color: #000000;\">.language \r\n<\/span><span style=\"font-weight: bold; color: #0000ff;\">PowerShell\r\n<\/span><\/pre>\n<pre><span style=\"font-weight: bold; color: #c00000;\">PS<\/span><span style=\"color: #000000;\"> C:\\&gt; <\/span><span style=\"color: #8b0000;\">$hashback<\/span><span style=\"color: #000000;\">.gettype().fullname\r\n<\/span><span style=\"color: #696969;\">System.Collections.Hashtable<\/span><\/pre>\n<\/blockquote>\n<p>&nbsp;<\/p>\n<p>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\u2019s available to me in every Windows PowerShell session that uses my profile.<\/p>\n<p>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.<\/p>\n<p><i>June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at <a href=\"mailto:juneb@sapien.com\">juneb@sapien.com<\/a>\u00a0and follow her on Twitter at <a href=\"https:\/\/twitter.com\/juneb_get_help\">@juneb_get_help<\/a>.<\/i><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I didn\u2019t make it to the PowerShell Summit 2014 in Europe this year, but I\u2019ve been getting much of the benefit by watching the Summit presentations on YouTube. After watching Windows PowerShell MVP Bartek Bielawski\u2019s excellent presentation, in which he reviews PSCustomObjects and dynamic type data, I was inspired to fix a shortcoming in the [&hellip;]<\/p>\n","protected":false},"author":31,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[25],"tags":[934,28],"class_list":["post-8019","post","type-post","status-publish","format-standard","hentry","category-windows-powershell","tag-juneb","tag-powershell"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/8019","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/users\/31"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/comments?post=8019"}],"version-history":[{"count":16,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/8019\/revisions"}],"predecessor-version":[{"id":8035,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/8019\/revisions\/8035"}],"wp:attachment":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/media?parent=8019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/categories?post=8019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/tags?post=8019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}