Private/Format-Columns.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
function Format-Columns {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [PsObject[]]$InputObject,
        [Object]$Property,
        [int]$Column,
        [int]$MaxColumn,
        [switch]$Autosize
    )

    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 | ForEach-Object { $_.($exp) } }
                    elseif ($exp -is [scriptblock]) { $ret = $ret | ForEach-Object { & $exp $_} }
                    else                            { throw 'Invalid Expression value' }
                }
                if ($p.FormatString) {
                    if ($p.FormatString -is [string]) {    $ret = $ret | ForEach-Object { $p.FormatString -f $_ } }
                    else {                              throw 'Invalid format string' }
                }
            }
            elseif ($p -is [scriptblock]) { $ret = $ret | ForEach-Object { & $p $_} }
            elseif ($p -is [string]) {      $ret = $ret | ForEach-Object { $_.$p } }
            elseif ($null -ne $p) {         throw 'Invalid -property type' }
            # in case there were some numbers, objects, etc., convert them to string
            $ret | ForEach-Object { $_.ToString() }
        }
        if (!$Column) { $Autosize = $true }
        $values = ProcessValues

        $valuesCount = @($values).Count
        if ($valuesCount -eq 1) {
            return $values
        }

        # from some reason the console host doesn't use the last column and writes to new line
        $consoleWidth          = $host.ui.RawUI.maxWindowSize.Width - 1
        $gutterWidth = 2

        # get length of the longest string
        $values | ForEach-Object -Begin { [int]$maxLength = -1 } -Process { $maxLength = [Math]::Max($maxLength,$_.Length) }

        # get count of columns if not provided
        if ($Autosize) {
            $Column         = [Math]::Max( 1, ([Math]::Floor(($consoleWidth/($maxLength+$gutterWidth)))) )
            $remainingSpace = $consoleWidth - $Column*($maxLength+$gutterWidth);
            if ($remainingSpace -ge $maxLength) {
                $Column++
            }
            if ($MaxColumn -and $MaxColumn -lt $Column) {
                $Column = $MaxColumn
            }
        }
        $countOfRows       = [Math]::Ceiling($valuesCount / $Column)
        $maxPossibleLength = [Math]::Floor( ($consoleWidth / $Column) )

        # cut too long values, considers count of columns and space between them
        $values = $values | ForEach-Object {
            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 | ForEach-Object { "{$($_-1),-$maxPossibleLength}" }) -join ''

        1..$countOfRows | ForEach-Object {
            $r    = $_-1
            $line = @(1..$Column | ForEach-Object { $values[$r + ($_-1)*$countOfRows] } )
            Write-Output "$($formatString -f $line)".PadRight($consoleWidth,' ')
        }
    }

    <#
    .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
        1..150 | Format-Columns -Autosize
    .EXAMPLE
        Format-Columns -Col 3 -Input 1..130
    .EXAMPLE
        Get-Process | Format-Columns -prop @{Expression='Handles'; FormatString='{0:00000}'} -auto
    .EXAMPLE
        Get-Process | Format-Columns -prop {$_.Handles} -auto
    .NOTES
        Name: Get-Columns
        Author: stej, http://twitter.com/stejcz
        Site: http://www.leporelo.eu/blog.aspx?id=powershell-formatting-format-wide-rotated-to-format-columns
        Lastedit: 2017-09-11
        Version 0.4 - 2017-09-11
        - removed color support and changed output from Write-Host to Write-Output
        Version 0.3 - 2017-04-24
        - added ForegroundColor and BackgroundColor
        Version 0.2 - 2010-01-14
        - added MaxColumn
        - fixed bug - displaying collection of 1 item was incorrect
        Version 0.1 - 2010-01-06
    #>

}