VerticalWide.psm1

function Format-VerticalWide {
    Param(
    $Column = 2,
    [string[]]$Properties = "Name",
    [Switch]$AutoSize)

    $array = @($input)
    $count = $array.Count

    <# Convert input object to PSCustomObject to write
     nulls so I can properly output blank cells #>


    #Build a ScriptBlock with variable number of properties like this:
    #'$array | ForEach-Object -Process {[PSCustomObject] @{$($Properties[0])=$_.$($Properties[0]);$($Properties[1])=$_.$($Properties[1])}}'

    $MainBlock = '$array | ForEach-Object -Process {[PSCustomObject]'

    $ProcessBlock = '@{'
    foreach ($n in 0..($Properties.Count-1)) {
        $ProcessBlock += '$($Properties[' + $n + '])=[string]$_.$($Properties[' + $n + ']);'
    }

    $ProcessBlock += '}}'

    $ScriptBlock = $MainBlock + $ProcessBlock

    $block = [ScriptBlock]::Create($ScriptBlock)
    $array = Invoke-Command $block

    # Get column widths based off the longest string in each column/property
    # Store each column width in array
    $colwidths = @()
    foreach ($n in 0..($Properties.Count-1)) {
        $proplengths = @()
        foreach ($a in $array) {
            if ($null -eq $a) {
                $proplengths += 0
            } else {
                $proplengths += $a.$($Properties[$n]).Length #Check for null
            }
        }
        $maxlength = $proplengths | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
        $colwidths += $maxlength
    }

    #AutoSize
    #Total max lengths of each property
    $coltotal = $colwidths | Measure-Object -sum | Select-Object -ExpandProperty Sum
    #Add double the count of elements to account for space in between sub-columns
    $coltotal += ($colwidths | Measure-Object | Select-Object -ExpandProperty Count)*2
    #Get char width of current console window
    $consolewidth = $host.UI.RawUI.WindowSize.Width
    #divide console width by total major column width
    $autocols = [Math]::Floor($consolewidth/$coltotal)

    if ($AutoSize) {
        $cols = $autocols
    } else {
        $cols = $Column
    }

    # limit columns allowed to half of count
    if ($cols -gt $count/2) {$cols = $count/2}

    $rows = [math]::Ceiling($count/$cols) #round up
    $colmod = $count%$cols

    $reordered = @() #initialize array

    <# Rearrange array to output vertically versus horizontally
    Horizontal
     1 | 2 | 3
     4 | 5 | 6
     7 | 8 | 9
    Vertical
     1 | 4 | 7
     2 | 5 | 8
     3 | 6 | 9
     #>

     if ([Math]::Ceiling($count/$rows) -eq $cols) {
        <# down, then over
        1..13 | %{[PSCustomObject]@{Name = $_}} | Format-VerticalWide -Column 4
            13 values with 4 columns
            1 | 5 | 9 | 13
            2 | 6 | 10 |
            3 | 7 | 11 |
            4 | 8 | 12 |
            default
        #>

        for ($i = 0; $i -lt $rows; $i++) {
            for ($j = 0; $j -lt $count; $j += $rows) {
                if ($i+$j -ge $count) {
                    #Blank cells for remainder of space on last column, lower right corner
                    $reordered += [PSCustomObject]@{Names = $null}
                } else {
                    $reordered += $array[$i+$j]
                }
            }
        }
     } else {
        <# spread
        1..13 | %{[PSCustomObject]@{Name = $_}} | Format-VerticalWide -Column 6
            13 values with 6 columns
            1 | 4 | 6 | 8 | 10 | 12
            2 | 5 | 7 | 9 | 11 | 13
            3 | | | | |
            stretch to number of columns specified
         #>

        for ($i = 0; $i -lt $rows; $i++) {
            for ($j = 0; $j -lt $count; $j += $rows) {
                if (($j/$rows -gt $colmod) -and ($colmod -ne 0)) {
                    $j--
                }
                if (($i+$j -ge $count) -or (($colmod -ne 0) -and ($i -eq $rows-1) -and (($j+1)/$rows -gt $colmod))) {
                    #Blank cells for remainder of space on last column, lower right corner
                    $reordered += [PSCustomObject]@{Names = $null}
                } else {
                    $reordered += $array[$i+$j]
                }
            }
        }
     }

    #Build ScriptBlock for variable amount of properties to pass to Format-Wide cmdlet

    $MainBlock = '$reordered | Format-Wide -Column $cols -Property '

    $ProcessBlock = '@{e={"'
    foreach ($n in 0..($Properties.Count-1)) {
        $ProcessBlock += '{' + $n + '} '
    }

    $ProcessBlock += '" -f '

    foreach ($n in 0..($Properties.Count-1)) {
        if ($n -ne $Properties.Count-1) {
            $ProcessBlock += '$_.$($Properties[' + $n + ']).ToString().PadRight($($colwidths[' + $n + ']),[char]32), ' #TODO: Check for Null?
        } else {
            $ProcessBlock += '$_.$($Properties[' + $n + '])}}'
        }
    }

    $ScriptBlock = $MainBlock + $ProcessBlock

    $block = [ScriptBlock]::Create($ScriptBlock)

    Write-Debug $ScriptBlock

    #Resulting script block should look like this:
    #$reordered | Format-Wide -Column $cols -Property @{e={"{0} {1}" -f $_.$($Properties[0]), $_.$($Properties[1])}}
    Invoke-Command -ScriptBlock $block

    <#
    .Synopsis
    Custom function for displaying data in vertical columns
    .DESCRIPTION
    Format-Wide organizes horizontally, similar to ls --format horizontal.
    Format-VerticalWide rearranges the pipeline input and re-outputs to Format-Wide
    so that data flows down rather than to the right. Similar to ls command in
    UNIX and Microsoft Word columns.
 
    Format-Wide layout
        Horizontal
        1 | 2 | 3
        4 | 5 | 6
        7 | 8 | 9
    Format-VerticalWide layout
        Vertical
        1 | 4 | 7
        2 | 5 | 8
        3 | 6 | 9
    .PARAMETER Column
    Specifies the number of columns in the display. You cannot use the AutoSize and Column parameters in the same command.
    .PARAMETER Properties
    Specifies the object properties that appear in the display and the order in which they appear.
    .PARAMETER AutoSize
    Adjusts the column size and number of columns based on the width of the data. Overrides Column property.
    .EXAMPLE
    Get-Process | Format-VerticalWide
 
 
    ApplicationFrameHost svchost
    AppVShNotify svchost
    armsvc svchost
    audiodg svchost
    Calculator svchost
    conhost svchost
    conhost svchost
    csrss svchost
    csrss svchost
    ctfmon svchost
    dasHost svchost
    dllhost svchost
    dllhost svchost
    ...
 
    .EXAMPLE
    Get-Process | Format-VerticalWide -Column 3
 
 
    ApplicationFrameHost powershell_ise svchost
    AppVShNotify Registry svchost
    armsvc RuntimeBroker svchost
    audiodg RuntimeBroker svchost
    Calculator RuntimeBroker svchost
    conhost RuntimeBroker svchost
    conhost RuntimeBroker svchost
    csrss RuntimeBroker svchost
    csrss RuntimeBroker svchost
    ctfmon RuntimeBroker svchost
    dasHost RuntimeBroker svchost
    dllhost SearchApp svchost
    dllhost SearchIndexer svchost
    dllhost Secure System svchost
    ...
 
 
    .EXAMPLE
    Get-Process | Format-VerticalWide -Column 3 -Properties Id,Name
 
 
    7988 ApplicationFrameHost 12700 RuntimeBroker 3252 svchost
    8916 AppVShNotify 14300 RuntimeBroker 3572 svchost
    4332 armsvc 14436 RuntimeBroker 3636 svchost
    7652 audiodg 15248 RuntimeBroker 3644 svchost
    2808 Calculator 10104 SearchApp 3784 svchost
    9296 conhost 9864 SearchIndexer 3832 svchost
    13352 conhost 72 Secure System 3840 svchost
    696 csrss 3524 SecurityHealthService 3924 svchost
    13884 csrss 4660 SecurityHealthSystray 3968 svchost
    5812 ctfmon 440 services 4120 svchost
    2356 dasHost 9464 SettingSyncHost 4232 svchost
    1288 dllhost 9896 SgrmBroker 4240 svchost
    8584 dllhost 428 ShellExperienceHost 4248 svchost
    14624 dllhost 12272 sihost 4256 svchost
    ...
    #>

} Export-ModuleMember -Function Format-VerticalWide