10

How can I change the column ordering of the output my code produces:

$apps = Import-CSV apps.csv
$computers = Import-CSV compobj.csv
foreach ($computer in $computers) {    
    $computerLob = $computer.lob
    $lobApps = $apps | ? {$_.lob -eq $computerLob }
    foreach ($app in $lobApps) {
        $computerHostname = $computer.hostname
        $appLocation = $app.location
        $installed=Test-Path "\\$computerHostname\$appLocation"      
        New-Object PSObject -Property @{
            Computer=$computer.hostname
            App=$app.appname
            Installed=$installed
        }
    }

Currently it's producing the columns in the following order: Installed,App,Computer.

I'd like to have it in the following order: Computer,App,Installed.

Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151
theshizy
  • 505
  • 3
  • 10
  • 31
  • BTW, you're missing a closing brace. I'm assuming you intended to have nested **foreach** loops, and the missing brace is at the end? – Adi Inbar Oct 27 '13 at 23:50
  • See [my answer](http://stackoverflow.com/a/19625295/897326) in [your another question](http://stackoverflow.com/questions/19624304/powershell-change-format-of-output). The answers provided here are good - just another way of doing it. – Victor Zakharov Oct 28 '13 at 00:46

5 Answers5

22

Powershell V3 added a type accelerator for [PSCustomObject] that creates objects using an ordered hash table, so the properties stay in the order they're declared:

[PSCustomObject] @{
  Computer=$computer.hostname
  App=$app.appname
  Installed=$installed
}
mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • +1. This is interesting. Any links to documentation? – Victor Zakharov Oct 28 '13 at 00:47
  • I can't remember where I read about that. It wasn't apparent in the MS docs that were released with V3. They also added a type accelereater [ordered] for creating ordered hash tables, but you don't have to use it with [PSCustomObject], it does it automatically. – mjolinor Oct 28 '13 at 01:01
  • Good to know - I am quite new to V3. Thank you for answering. – Victor Zakharov Oct 28 '13 at 01:05
  • Another +1...I just tried it out, it works great. Makes things significantly more compact than in v2 if you have a lot of properties and order matters. – Adi Inbar Oct 28 '13 at 23:30
  • 1
    Late to the party but for those wondering about Powershell Accelerators, yes you can enumerate the available accelerators by using `[psobject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::get` Thanks go to [this article](http://blogs.technet.com/b/heyscriptingguy/archive/2013/07/08/use-powershell-to-find-powershell-type-accelerators.aspx) – user4317867 Dec 20 '15 at 00:26
  • Don't you have to use the `[ordered]` keyword for this to work? – Matt Sep 25 '17 at 18:45
  • @Matt - no you don't. The constructor appears to be designed require an ordered hash table, so if you don't explicitly specify [ordered], Powershell type conversion will automatically coerce it to [ordered] for you. – mjolinor Sep 26 '17 at 13:55
19

If you want to ensure the output of an object occurs in a certain order i.e. formatted a certain way then use PowerShell's formatting commands.

$obj = [pscustomobject]@{Computer=$computer.hostname;App=$app.appname;Installed=$installed} 
$obj | Format-Table Computer,App,Installed

What's more you can better control the output e.g.:

$obj | Format-Table Computer,App,Installed -AutoSize

Or control field width & alignment:

$obj | Format-Table @{label='Computer';expression={$_.Computer};width=20;alignment='right'},App,Installed

Frankly I think it is a better practice to use the formatting commands to get the output order you want rather than to rely upon the order in which the properties are created.

Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • Using Format-Table is quite verbose and it is in fact unnecessary in PowerShell 3 and above for objects that you create and display yourself, and when writing modules it would be good practice to be aware of this and setup a default order which is handy for people reading your output over and over. – Eric Nov 12 '15 at 19:27
  • This is OK for output to the host, but will not work for exporting to a file as it corrupts the object in the pipeline. See the answer by @Emiliano Poggi. – Chiramisu Jul 02 '16 at 00:48
  • @Chiramisu - You can take the output of Format-Table, pipe it to Out-String, then pipe that to Set-Content. One thing I often do is this: $array | Format-Table | Out-String | Tee-Object -Variable $table_data | Write-Host {newline} $table_data | Set-Content $outputfile – Mike Christiansen Dec 13 '18 at 15:44
6

The problem is that you're adding the properties using a hashtable, and hashtables don't preserve order. You can do one of two things:

1. Add the properties in a manner that will set the order (the last line is to output the object to the pipeline):

$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $computer.hostname
$obj | Add-Member -MemberType NoteProperty -Name App -Value $app.appname
$obj | Add-Member -MemberType NoteProperty -Name Installed -Value $installed
$obj

2. Determine the order at the time you output the object to the pipeline using Select-Object:

New-Object PSObject -Property @{
  Computer=$computer.hostname
  App=$app.appname
  Installed=$installed
} | select Computer,App,Installed

Which one is preferable depends on how much you'll be working with the object. If, as your question implies, you're only using the PSObject in order to display the information in tabular format, the second method is quicker. If you're going to output the object multiple times in different parts of the script, the first method allows you to simply output it as $obj rather than having to pipe to select every time.

Note also that the second method can be split up like this if you don't want to output the object immediately after populating it:

$obj = New-Object PSObject -Property @{
   Computer=$computer.hostname
    App=$app.appname
    Installed=$installed
} 

[...do other stuff...]

$obj | select Computer,App,Installed
Adi Inbar
  • 12,097
  • 13
  • 56
  • 69
  • Using `Select-Object` is overkill for formatting output. For that it is best to use the "formatting commands" i.e. Format-Table in this case. In this scenario, Select-Object creates a completely new `pscustomobject` which is unnecessary if the goal is to simply format the output. – Keith Hill Oct 28 '13 at 03:36
  • 1
    @KeithHill That's actually the very reason I used **Select-Object** rather than **Format-Table** -- because I didn't want to assume that wanting to order the columns implies that the sole use of the function is to display the data and the output would never need to be piped to anything. I think it's better practice to write functions that retrieve a single set of data to output objects to the pipeline, making them more flexible and repurposable, and use **ft** only if you need its additional formatting features or if you're certain the code will only ever be used to display final output. – Adi Inbar Oct 28 '13 at 23:25
5

Format-Table it's a good solution when you need to display your object fields in a specific order, but it will change the obect and you won't be able to pipe your object, for example when exporting to csv (Export-Csv).

In the case you just want to change "the order of the fields in the object" use Select-Object. This will preserve object type and fields, and still you will be able to pipe the object to other cmdlets.

Emiliano Poggi
  • 24,390
  • 8
  • 55
  • 67
0

A more universal way to change the order is using the Select-Object cmdlet with the list of properties in the required order.

See the example:

PS> $ObjectList = 1..3 | 
% {New-Object psobject -Property @{P2="Object $_ property P2"; P1="Object $_ property P1"}}
PS> $ObjectList

P2                   P1
--                   --
Object 1 property P2 Object 1 property P1
Object 2 property P2 Object 2 property P1
Object 3 property P2 Object 3 property P1


PS> $ObjectList | Select-Object P1,P2

P1                   P2
--                   --
Object 1 property P1 Object 1 property P2
Object 2 property P1 Object 2 property P2
Object 3 property P1 Object 3 property P2

The full form of these commands is the following:

$ObjectList = 1..3 | 
ForEach-Object -Process {New-Object -TypeName psobject -Property @{P2="Object $_ property P2"; P1="Object $_ property P1"}}
$ObjectList | Select-Object -Property P1,P2