Private/Select-W365CliObject.ps1

function Select-W365CliObject {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [object[]]$InputObject,

        [Parameter(Mandatory)]
        [string]$Title,

        [Parameter(Mandatory)]
        [string[]]$DisplayProperties,

        [string[]]$FilterProperties = $DisplayProperties,

        [scriptblock]$LabelScript,

        [string[]]$DetailProperties,

        [scriptblock]$DetailScript,

        [scriptblock]$ActionScript,

        [string[]]$ActionLabels,

        [scriptblock]$ActionLabelsScript,

        [scriptblock]$EmptyActionScript,

        [string[]]$EmptyActionLabels,

        [scriptblock]$NewActionScript,

        [string]$NewActionLabel,

        [scriptblock]$SummaryScript,

        [scriptblock]$RefreshScript,

        [string[]]$Breadcrumb,

        [string]$ColumnHeader,

        [switch]$ViewOnly,

        [ValidateRange(3, 40)]
        [int]$PageSize = 8
    )

    begin { }

    process {
        $filter = ''
        $selectedIndex = 0

        while ($true) {
            $items = @($InputObject)

            if (-not [string]::IsNullOrWhiteSpace($filter)) {
                $items = @(
                    $items | Where-Object {
                        $item = $_
                        foreach ($property in $FilterProperties) {
                            $value = $item.$property
                            if ($null -ne $value -and "$value" -like "*$filter*") {
                                return $true
                            }
                        }

                        return $false
                    }
                )
            }

            if ($items.Count -eq 0) {
                $selectedIndex = 0
            }
            elseif ($selectedIndex -ge $items.Count) {
                $selectedIndex = $items.Count - 1
            }

            $windowStart = 0
            if ($items.Count -gt $PageSize) {
                $halfPage = [math]::Floor($PageSize / 2)
                $maxStart = $items.Count - $PageSize
                $windowStart = [math]::Max(0, [math]::Min($selectedIndex - $halfPage, $maxStart))
            }

            $windowEnd = if ($items.Count -eq 0) { -1 } else { [math]::Min($items.Count - 1, $windowStart + $PageSize - 1) }

            Clear-Host
            Write-Host ''
            if ($Breadcrumb -and $Breadcrumb.Count -gt 0) {
                Write-Host ("Location: {0}" -f ($Breadcrumb -join ' > ')) -ForegroundColor DarkYellow
                Write-Host ''
            }

            Write-Host $Title -ForegroundColor Cyan

            if ($SummaryScript) {
                $summaryLines = @(& $SummaryScript -AllItems $InputObject -VisibleItems $items -Filter $filter -SelectedIndex $selectedIndex)
                foreach ($summaryLine in $summaryLines) {
                    if (-not [string]::IsNullOrWhiteSpace($summaryLine)) {
                        Write-Host $summaryLine -ForegroundColor DarkGray
                    }
                }
            }
            else {
                Write-Host ("Showing {0} of {1}" -f $items.Count, $InputObject.Count) -ForegroundColor DarkGray
            }

            $refreshHint = if ($RefreshScript) { ' | R refresh' } else { '' }
            $newHint = if ($NewActionScript -and $NewActionLabel) { " | N $NewActionLabel" } else { '' }
            Write-Host "Up/Down move | PgUp/PgDn page | Enter select/details | D details | / or F filter | C clear$refreshHint$newHint | Esc back" -ForegroundColor DarkGray
            if ($filter) {
                Write-Host "Filter: $filter"
            }
            Write-Host ''

            if ($items.Count -gt $PageSize) {
                Write-Host ("Showing {0}-{1} of {2}" -f ($windowStart + 1), ($windowEnd + 1), $items.Count) -ForegroundColor DarkGray
            }

            if ($ColumnHeader) {
                Write-Host (" {0}" -f $ColumnHeader) -ForegroundColor DarkCyan
                Write-Host (" {0}" -f ('-' * $ColumnHeader.Length)) -ForegroundColor DarkCyan
            }

            if ($items.Count -eq 0) {
                Write-Host ' No rows.' -ForegroundColor DarkGray
                if ($EmptyActionScript -and $EmptyActionLabels -and $EmptyActionLabels.Count -gt 0) {
                    Write-Host ''
                    Write-Host 'Actions' -ForegroundColor Cyan
                    Write-Host 'Use Enter to run the highlighted action, Esc to go back.' -ForegroundColor DarkGray
                    Write-Host (" > {0}" -f $EmptyActionLabels[0]) -ForegroundColor White -BackgroundColor DarkCyan
                }
            }
            else {

                for ($index = $windowStart; $index -le $windowEnd; $index++) {
                    $item = $items[$index]
                    $labelLines = @(
                        if ($LabelScript) {
                            & $LabelScript $item
                        }
                        else {
                            $labelParts = foreach ($property in $DisplayProperties) {
                                $value = $item.$property
                                if ($null -ne $value -and "$value" -ne '') {
                                    "$property=$value"
                                }
                            }

                            $labelParts -join '; '
                        }
                    )

                    $firstLine = if ($labelLines.Count -gt 0) { $labelLines[0] } else { "$item" }
                    $prefix = if ($index -eq $selectedIndex) { '>' } else { ' ' }
                    $line = (" {0} {1}" -f $prefix, $firstLine)

                    if ($index -eq $selectedIndex) {
                        Write-Host $line -ForegroundColor White -BackgroundColor DarkCyan
                    }
                    else {
                        Write-Host $line
                    }

                    foreach ($line in @($labelLines | Select-Object -Skip 1)) {
                        if (-not [string]::IsNullOrWhiteSpace($line)) {
                            Write-Host (" {0}" -f $line) -ForegroundColor DarkGray
                        }
                    }
                }

                if ($NewActionScript -and $NewActionLabel) {
                    Write-Host ''
                    Write-Host 'Actions' -ForegroundColor Cyan
                    Write-Host 'Press N to run a list action.' -ForegroundColor DarkGray
                    Write-Host (" N. {0}" -f $NewActionLabel) -ForegroundColor White
                }
            }

            $key = [Console]::ReadKey($true)
            switch ($key.Key) {
                'UpArrow' {
                    if ($selectedIndex -gt 0) {
                        $selectedIndex--
                    }
                }
                'DownArrow' {
                    if ($selectedIndex -lt ($items.Count - 1)) {
                        $selectedIndex++
                    }
                }
                'PageUp' {
                    $selectedIndex = [math]::Max(0, $selectedIndex - $PageSize)
                }
                'PageDown' {
                    $selectedIndex = [math]::Min($items.Count - 1, $selectedIndex + $PageSize)
                }
                'Home' {
                    $selectedIndex = 0
                }
                'End' {
                    $selectedIndex = [math]::Max(0, $items.Count - 1)
                }
                'Enter' {
                    if ($items.Count -eq 0) {
                        if ($EmptyActionScript -and $EmptyActionLabels -and $EmptyActionLabels.Count -gt 0) {
                            & $EmptyActionScript $EmptyActionLabels[0]
                        }
                        continue
                    }

                    if ($ViewOnly) {
                        Show-W365CliObjectDetail -InputObject $items[$selectedIndex] -Title $Title -DetailProperties $DetailProperties -DetailScript $DetailScript -ActionScript $ActionScript -ActionLabels $ActionLabels -ActionLabelsScript $ActionLabelsScript -Breadcrumb ($Breadcrumb + $Title)
                        continue
                    }

                    return $items[$selectedIndex]
                }
                'RightArrow' {
                    if ($items.Count -gt 0) {
                        Show-W365CliObjectDetail -InputObject $items[$selectedIndex] -Title $Title -DetailProperties $DetailProperties -DetailScript $DetailScript -ActionScript $ActionScript -ActionLabels $ActionLabels -ActionLabelsScript $ActionLabelsScript -Breadcrumb ($Breadcrumb + $Title)
                    }
                }
                'Escape' {
                    return $null
                }
                'LeftArrow' {
                    return $null
                }
                default {
                    if ($key.KeyChar -match '^[bBqQ]$') {
                        return $null
                    }

                    if ($key.KeyChar -match '^[dD]$' -and $items.Count -gt 0) {
                        Show-W365CliObjectDetail -InputObject $items[$selectedIndex] -Title $Title -DetailProperties $DetailProperties -DetailScript $DetailScript -ActionScript $ActionScript -ActionLabels $ActionLabels -ActionLabelsScript $ActionLabelsScript -Breadcrumb ($Breadcrumb + $Title)
                        continue
                    }

                    if ($key.KeyChar -match '^[cC]$') {
                        $filter = ''
                        $selectedIndex = 0
                        continue
                    }

                    if ($NewActionScript -and $NewActionLabel -and $key.KeyChar -match '^[nN]$') {
                        & $NewActionScript $NewActionLabel
                        continue
                    }

                    if ($RefreshScript -and $key.KeyChar -match '^[rR]$') {
                        Clear-Host
                        Write-Host ''
                        Write-Host "Refreshing $Title..." -ForegroundColor Cyan
                        $InputObject = @(& $RefreshScript)
                        $selectedIndex = 0
                        continue
                    }

                    if ($key.KeyChar -match '^[fF/]$') {
                        Write-Host ''
                        $filter = (Read-Host 'Filter text').Trim()
                        $selectedIndex = 0
                        continue
                    }

                    $number = 0
                    if ([int]::TryParse($key.KeyChar, [ref]$number) -and $number -ge 1 -and $number -le [math]::Min(9, $items.Count)) {
                        $targetIndex = $windowStart + $number - 1
                        if ($targetIndex -lt $items.Count) {
                            if ($ViewOnly) {
                                Show-W365CliObjectDetail -InputObject $items[$targetIndex] -Title $Title -DetailProperties $DetailProperties -DetailScript $DetailScript -ActionScript $ActionScript -ActionLabels $ActionLabels -ActionLabelsScript $ActionLabelsScript -Breadcrumb ($Breadcrumb + $Title)
                                continue
                            }

                            return $items[$targetIndex]
                        }
                    }
                }
            }
        }
    }

    end { }
}