1

I am trying to create an nicely formatted Txt file (has to be Txt cannot be CSV) from a hashtable.

Here is the code that I have that creates the text file

$hashTable.GetEnumerator() | Sort-Object Name |
ForEach-Object {"{0} `t`t: {1} `t`t: {2} `t`t: {3} `t`t: {4}" -f $_.DisplayName,$_.EmployeeNo,$_.Email, $_.SamName,$_.Upn}|
Add-Content $textFilePath

As you can see - when someone is input into the Txt file with a long name it messes up the formatting because the tabs just get shifted along. enter image description here

I'm looking for a solution that dynamically sets the width of the columns in a text file.

Joe BW
  • 113
  • 4
  • 14
  • As an aside: I assume it's just a posting artifact, but your code doesn't work as-is, because what is seen as `$_` in the pipeline isn't an AD user object directly, it is a `System.Collections.DictionaryEntry` instance whose `.Value` property contains the AD user object. In other word, instead of `$_.DisplayName` it would have to be `$_.Value.DisplayName`, and so on. – mklement0 Oct 29 '20 at 13:00

1 Answers1

3

Use PowerShell's output-formatting system, whose Format-Table cmdlet produces human-readable, column-aligned representations, but note that these are generally not suitable for later programmatic processing:

($hashTable.GetEnumerator() | Sort-Object Name).Value | 
  Format-Table -AutoSize -Property DisplayName, EmployeeNo, Email, SamName, Upn | 
    Out-File $textFilePath -Encoding Utf8 -Width 1024 
  • $hashTable.GetEnumerator() | Sort-Object Name enumerates the hashtable entries as System.Collections.DictionaryEntry entries and sorts them by their .Name (.Key) property.

    • Note that the .GetEnumerator() call is necessary to sort the hash table entries by their key (name); without it, the hash table as a whole would be sent as a single object through the pipeline, in which case the Sort-Object would effectively be a no-op - by definition, there's nothing to sort if there's only one object; compare the output from:
      @{ zach = 1; moe = 2; aardvark = 0 } | Sort-Object Name
      to the output from:
      @{ zach = 1; moe = 2; aardvark = 0 }.GetEnumerator() | Sort-Object Name
      In the first command, the Sort-Object call has no effect.
  • .Value then extracts the entries' values (this could also be done in streaming fashion with ... | ForEach-Object Value).

  • The values appears to be Active Directory user objects, so you can just pass a list of the desired property names to Format-Table, which form the output-table column names, whereas the corresponding property values form the column values.

  • Note that Out-File rather than Add-Content (Set-Content) is used, because only it automatically converts the formatting objects that Format-Table outputs to their intended string representations, as you would see in the console (terminal).

    • -Width 1024 ensures that the table lines aren't truncated based on whatever the width of the current console window happens to be; adjust as needed.
    • -Encoding Utf8 is used as an example to control the output encoding; note that Windows PowerShell defaults to "Unicode" (UTF-16LE), whereas the default is BOM-less UTF-8 in PowerShell [Core] v6+. See this answer for more information about default character encodings in Windows PowerShell vs. PowerShell [Core].
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Does `Format-Table` have a `-Width` parameter? I think maybe that goes with `Out-File`. Checked documentation and tested in 5.1 and 7.0. I'm also not sure we need `.GetEnumerator()` , in this scenario `$hashTable.Values | Sort-Object Name | ...` seems to work just as well. Is there a reason to specify the encoding? – Steven Oct 28 '20 at 23:46
  • Yes - thank you @Steven: the `-Width` belongs with the `Out-File` command - fixed. As for your other points: please see my update. – mklement0 Oct 28 '20 at 23:55
  • Ah I see my mistake. In my test data I included a name property. So sorting on the underlying object’s name property rather than the key of the hash. Thanks! – Steven Oct 29 '20 at 03:16
  • I understand. In my erroneous experiment I didn't send the hash table in whole down the pipeline. I sent the `.Values` property of the hash table. Should the objects stored in the hash have a name property, like an AD users, PwSh would sort on it. Of course, that's not the same as sorting on the key. My error was in creating test data that indeed had a name property when in fact I didn't know what the key was. And, the rest followed. – Steven Oct 29 '20 at 12:06
  • Got it, @Steven. What probably added to the confusion is that the code in the question doesn't work as-is, because instead of `$_.DisplayName` it would have to be `$_.Value.DisplayName`, and so on. – mklement0 Oct 29 '20 at 12:59