Powershell hashtable does not write to file as expected - receive only "System.Collections" rows
Asked Answered
B

7

12

Can someone please explain Why my first examples don't work, and why adding in a ForEach-Object solves the problem? Thanks in advance!


I parsed the return from a command into a hashtable (sample at end of post) and want to log the information to a file as part of my processing. I know that $ht.GetEnumerator() | Sort-Object Name will return the full hash to screen, sorted. However, once I try sending things to file, it breaks.

$ht | Add-Content log.txt

only logs a single row of System.Collections.Hashtable. So, I've also tried

$ht.GetEnumerator() | Sort-Object Name | Add-Content log.txt 

and end up with rows of

System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry

So then I tried to loop through and handle each individually with

foreach ($key in $ht.keys) {
Add-Content log.txt "$key : $ht.$key" }

and end up with

Server address : System.Collections.Hashtable.Server address
Client address : System.Collections.Hashtable.Client address
User name : System.Collections.Hashtable.User name

Solved with:

$ht.GetEnumerator() | Sort-Object Name |
ForEach-Object {"{0} : {1}" -f $_.Name,$_.Value} |
Add-Content log.txt 

For reference, the hashtable sample:

$ht = @{
    "Server address" = "server.net";
    "Client address" = "10.20.121.153";
    "User name" = "myuser"
}
Benzaldehyde answered 10/5, 2011 at 18:14 Comment(0)
G
15

Answering the why part, you obviously have a solution :)

In your first example

$ht | Add-Content log.txt

PowerShell takes $ht and tries to somehow convert it to a string so that it can be stored via Add-Content. Because there is no conversion defined for the hashtable, only the type name is returned from the conversion. Same as for example new-Object Random|Add-Content d:\log.txt. Again, only type name is written.

Next

$ht.GetEnumerator() | Sort-Object Name | Add-Content log.txt 

is similar. GetEnumerator returns object that is used for iteration; objects of type System.Collections.DictionaryEntry are returned. Again, there is no conversion to string, so type names are returned.

Personally, I think PowerShell should be smart enough and help here. The question is "how?". Designers probably didn't want to hardcode the output. It might be "{key}: {value}" or "{key} = {value}", or "{key}/{value}", or ... The format is not clear, so they left it for us to decide and format as you did it with the foreach statement.

Galicia answered 10/5, 2011 at 20:36 Comment(1)
Thanks for the explanation - I knew it had to be something with the conversion to strings, I just was not grasping the mechanisms involved. The combination of your elucidation and the more specific examples from everyone have helped immensely!Benzaldehyde
X
11

I agree with mjolinor... just not enough points to vote up... plus i'll add that you dont need the GetEnumerator

$ht | out-string | add-content log.txt

will do it.

Xiaoximena answered 29/6, 2011 at 12:55 Comment(0)
D
6

Your first example does not work, or better, partially works, because you are trying to get a property value within the string. Normally, inside strings, the parser is able to resolve only direct variables (like $key). To resolve more complex variable you need parenthesis.

For the loop, this should work:

foreach ($key in $ht.keys) {
Add-Content log.txt "$key : $($ht.$key)" }

or even better

$ht.keys | %{ Add-Content log.txt "$_ : $($ht.$_)" }
Deandre answered 10/5, 2011 at 18:50 Comment(1)
Thanks for the one-liner solution - the brevity is nice. Though I think after reading the others, I'm leaning towards combining your abbreviations with my sort and slightly more explicit format for readability.Benzaldehyde
A
6

As you can see in Microsoft documentation a hash table is simply a collection of name-value pairs.

So $ht is really System.Collections.Hashtable composed of System.Collections.DictionaryEntry.

A good way to use it is

foreach ($i in $ht.keys)
{
  add-content log.txt ("{0} {1}" -f $i, $ht[$i])
}
Arithmomancy answered 10/5, 2011 at 18:55 Comment(1)
Ah... so my original loop was so close. So forcing the formatting converts to strings that add-content handles gracefully. Gotcha.Benzaldehyde
D
3

How about:

 $ht.GetEnumerator() | Sort-Object Name | out-string | Add-Content log.txt 
Decathlon answered 10/5, 2011 at 19:2 Comment(1)
Interesting - this also has the benefit of spitting out the headers as well - it'll come in handy when I want those in the future - thanks!Benzaldehyde
I
0

You can write a "raw" dump of a hash table to an ASCII file using Out-File:

$data = @{
  Name = "Something"
  Type = "123"
}
$data | Out-File "myfile.txt" -Encoding ascii
Issacissachar answered 26/2, 2018 at 17:26 Comment(1)
This does not work for any significantly complex hash tables.Sorely
K
0

Microsoft recommends to take a clone of the keys before iterating over them!

This is copy/paste from https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-hashtable?view=powershell-7.4

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

So in your case it should look like :

foreach ($key in $ht.keys.Clone()) {
   Add-Content log.txt "$key : $ht.$key" 
}
Keck answered 25/4 at 4:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.