From time to time I use Format-Wide to display list of some data. It works great but sometimes my eyes have to jump quite a long distance between the columns – that's because in some cases the order matters. Example:

[1]: 'The basic promise behind implicit remoting is that you can work '+
   'with remote commands using local syntax.' -split ' ' | Format-Wide -force -prop {$_} -col 3
  
  The                        basic                      promise
  behind                     implicit                   remoting
  is                         that                       you
  can                        work                       with
  remote                     commands                   using
  local                      syntax.

In case you have to read the values in correct order, you will not feel comfortable, right?
So, check this:

[2]: 'The basic promise behind implicit remoting is that you can work '+
   'with remote commands using local syntax.' -split ' ' | . Format-Columns -col 3
  
  The                       is                        remote
  basic                     that                      commands
  promise                   you                       using
  behind                    can                       local
  implicit                  work                      syntax.
  remoting                  with

This version reads much better. You just follow whole columns, one by one.

Code

In case you find it helpful, here is the code:

function Format-Columns {
################################################################
#.Synopsis
#  Formats incoming data to columns.
#.Description
#  It works similarly as Format-Wide but it works vertically. Format-Wide outputs
#  the data row by row, but Format-Columns outputs them column by column.
#.Parameter Property
#  Name of property to get from the object.
#  It may be 
#   -- string - name of property.
#   -- scriptblock
#   -- hashtable with keys 'Expression' (value is string=property name or scriptblock)
#      and 'FormatString' (used in -f operator)
#.Parameter Column
#  Count of columns
#.Parameter Autosize
#  Determines if count of columns is computed automatically.
#.Parameter MaxColumn
#  Maximal count of columns if Autosize is specified
#.Parameter InputObject
#  Data to display
#.Example
#  PS> 1..150 | Format-Columns -Autosize
#.Example 
#  PS> Format-Columns -Col 3 -Input 1..130
#.Example
#  PS> Get-Process | Format-Columns -prop @{Expression='Handles'; FormatString='{0:00000}'} -auto
#.Example
#  PS> Get-Process | Format-Columns -prop {$_.Handles} -auto
#.Notes
# Name: Get-Columns
# Author: stej, http://twitter.com/stejcz
# Lastedit: 2010-01-14
# Version 0.2 - 2010-01-14
#  - added MaxColumn
#  - fixed bug - displaying collection of 1 item was incorrect
# Version 0.1 - 2010-01-06
################################################################
    param(
        [Parameter(Mandatory=$false,Position=0)][Object]$Property,
        [Parameter()][switch]$Autosize,
        [Parameter(Mandatory=$false)][int]$Column,
        [Parameter(Mandatory=$false)][int]$MaxColumn,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)][PsObject[]]$InputObject
    )
    begin   { $values = @() }
    process { $values += $InputObject }
    end {
        function ProcessValues {
            $ret = $values
            $p = $Property
            if ($p -is [Hashtable]) {
                $exp = $p.Expression
                if ($exp) {
                    if ($exp -is [string])          { $ret = $ret | % { $_.($exp) } }
                    elseif ($exp -is [scriptblock]) { $ret = $ret | % { & $exp $_} }
                    else                            { throw 'Invalid Expression value' }
                }
                if ($p.FormatString) {
                    if ($p.FormatString -is [string]) {    $ret = $ret | % { $p.FormatString -f $_ } }
                    else {                              throw 'Invalid format string' }
                }
            }
            elseif ($p -is [scriptblock]) { $ret = $ret | % { & $p $_} }
            elseif ($p -is [string]) {      $ret = $ret | % { $_.$p } }
            elseif ($p -ne $null) {         throw 'Invalid -property type' }
            # in case there were some numbers, objects, etc., convert them to string
            $ret | % { $_.ToString() }
        }
        function Base($i) { [Math]::Floor($i) }
        function Max($i1, $i2) {  [Math]::Max($i1, $i2) }
        if (!$Column) { $Autosize = $true }
        $values = ProcessValues
        
        $valuesCount = @($values).Count
        if ($valuesCount -eq 1) {
            $values | Out-Host
            return
        }
        
        # from some reason the console host doesn't use the last column and writes to new line
        $consoleWidth          = $host.ui.RawUI.maxWindowSize.Width -1; 
        $spaceWidthBetweenCols = 2
            
        # get length of the longest string
        $values | % -Begin { [int]$maxLength = -1 } -Process { $maxLength = Max $maxLength $_.Length }
        
        # get count of columns if not provided
        if ($Autosize) {
            $Column         = Max (Base ($consoleWidth/($maxLength+$spaceWidthBetweenCols))) 1
            $remainingSpace = $consoleWidth - $Column*($maxLength+$spaceWidthBetweenCols);
            if ($remainingSpace -ge $maxLength) { 
                $Column++ 
            }
            if ($MaxColumn -and $MaxColumn -lt $Column) {
                Write-Debug "Columns corrected to $MaxColumn (original: $Column)"
                $Column = $MaxColumn
            }
        }
        $countOfRows       = [Math]::Ceiling($valuesCount / $Column)
        $maxPossibleLength = Base ($consoleWidth / $Column)
        
        # cut too long values, considers count of columns and space between them
        $values = $values | % { 
            if ($_.length -gt $maxPossibleLength) { $_.Remove($maxPossibleLength-2) + '..' }
            else { $_ }
        }
        
        #add empty values so that the values fill rectangle (2 dim array) without space
        if ($Column -gt 1) {
            $values += (@('') * ($countOfRows*$Column - $valuesCount))
        }
        # in case there is only one item, make it array
        $values = @($values)
        <#
        now we have values like this: 1, 2, 3, 4, 5, 6, 7, ''
        and we want to display them like this:
        1 3 5 7
        2 4 6 ''
        #>
        
        $formatString = (1..$Column | %{"{$($_-1),-$maxPossibleLength}"}) -join ''
        1..$countOfRows | % { 
            $r    = $_-1
            $line = @(1..$Column | % { $values[$r + ($_-1)*$countOfRows]} )
            $formatString -f $line | Out-Host
        }
    }
}

At at the end one (almost) real example:

[3]: Get-Process | 
    Sort-Object Handles -desc | 
    Select-Object -first 30 | 
    Format-Columns -Property { '{0,-10} - {1,5}' -f $_.Name,$_.Handles} -auto
  
  System     -  2289    firefox    -   690    taskeng    -   451    MSASCui    -   360
  svchost    -  1216    lsass      -   672    cmdagent   -   444    PCSuite    -   347
  explorer   -  1122    svchost    -   642    Jing       -   441    procexp    -   341
  ScriptEditor -   870  Skype      -   628    CompPtcVUI -   431    svchost    -   333
  powershell -   867    powershell -   565    svchost    -   424    svchost    -   328
  csrss      -   745    svchost    -   528    svchost    -   414    spoolsv    -   312
  csrss      -   727    svchost    -   500    svchost    -   410
  trillian   -   708    upeksvr    -   482    wlanext    -   397

Download

Meta: 2010-01-07, Pepa