Public/Show-Menu.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<#
 
.SYNOPSIS
Shows an interactive menu to the user and returns the chosen item or item index.
 
.DESCRIPTION
Shows an interactive menu on supporting console hosts. The user can interactively
select one (or more, in case of -MultiSelect) items. The cmdlet returns the items
itself, or its indices (in case of -ReturnIndex).
 
The interactive menu is controllable by hotkeys:
- Arrow up/down: Focus menu item.
- Enter: Select menu item.
- Page up/down: Go one page up or down - if the menu is larger then the screen.
- Home/end: Go to the top or bottom of the menu.
- Spacebar: If in multi-select mode (MultiSelect parameter), toggle item choice.
 
Not all console hosts support the interactive menu (PowerShell ISE is a well-known
host which doesn't support it). The console host needs to support the ReadKey method.
The default PowerShell console host does this.
 
.PARAMETER MenuItems
Array of objects or strings containing menu items. Must contain at least one item.
Must not contain $null.
 
The items are converted to a string for display by the MenuItemFormatter parameter, which
does by default a ".ToString()" of the underlying object. It is best for this string
to fit on a single line.
 
The array of menu items may also contain unselectable separators, which can be used
to visually distinct menu items. You can call Get-MenuSeparator to get a separator object,
and add that to the menu item array.
 
.PARAMETER ReturnIndex
Instead of returning the object(s) that has/have been chosen, return the index/indices
of the object(s) that have been chosen.
 
.PARAMETER MultiSelect
Allow the user to select multiple items instead of a single item.
 
.PARAMETER ItemFocusColor
The console color used for focusing the active item. This by default green,
which looks good on both default PowerShell-blue and black consoles.
 
.PARAMETER MenuItemFormatter
A function/scriptblock which accepts a menu item (from the MenuItems parameter)
and returns a string suitable for display. This function will be called many times,
for each menu item once.
 
This parameter is optional and by default executes a ".ToString()" on the object.
If you control the objects that you pass in MenuItems, then you want to probably
override the ToString() method. If you don't control the objects, then this parameter
is very useful.
 
.INPUTS
 
None. You cannot pipe objects to Show-Menu.
 
.OUTPUTS
 
Array of chosen menu items or (if the -ReturnIndex parameter is given) the indices.
 
.LINK
 
https://github.com/Sebazzz/PSMenu
 
.EXAMPLE
 
Show-Menu @("option 1", "option 2", "option 3")
 
.EXAMPLE
 
Show-Menu -MenuItems $(Get-NetAdapter) -MenuItemFormatter { $Args | Select -Exp Name }
 
.EXAMPLE
 
Show-Menu @("Option A", "Option B", $(Get-MenuSeparator), "Quit")
 
#>

function Show-Menu {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory, Position = 0)][Array] $MenuItems,
        [Switch]$ReturnIndex, 
        [Switch]$MultiSelect, 
        [ConsoleColor] $ItemFocusColor = [ConsoleColor]::Green,
        [ScriptBlock] $MenuItemFormatter = { Param($M) Format-MenuItemDefault $M }
    )

    Test-HostSupported
    Test-MenuItemArray -MenuItems $MenuItems

    # Current pressed virtual key code
    $VKeyCode = 0

    # Initialize valid position
    $Position = Get-WrappedPosition $MenuItems -Position 0 -PositionOffset 1

    $CurrentSelection = @()
    $CursorPosition = [System.Console]::CursorTop
    
    try {
        [System.Console]::CursorVisible = $False # Prevents cursor flickering

        # Body
        $WriteMenu = {
            ([ref]$MenuHeight).Value = Write-Menu -MenuItems $MenuItems `
                -MenuPosition $Position `
                -MultiSelect:$MultiSelect `
                -CurrentSelection:$CurrentSelection `
                -ItemFocusColor $ItemFocusColor `
                -MenuItemFormatter $MenuItemFormatter
        }
        $MenuHeight = 0

        & $WriteMenu
        While ($True) {
            If (Test-KeyEscape $VKeyCode) {
                Return $null
            }

            if (Test-KeyEnter $VKeyCode) {
                Break
            }

            $CurrentPress = Read-VKey
            $VKeyCode = $CurrentPress.VirtualKeyCode

            If (Test-KeySpace $VKeyCode) {
                $CurrentSelection = Toggle-Selection $Position $CurrentSelection
            }

            $Position = Get-PositionWithVKey -MenuItems $MenuItems -Position $Position -VKeyCode $VKeyCode

            If (!$(Test-KeyEscape $VKeyCode)) {
                [System.Console]::SetCursorPosition(0, [Console]::CursorTop - $MenuHeight)
                & $WriteMenu
            }
        }
    }
    finally {
        [System.Console]::CursorVisible = $true
    }

    if ($ReturnIndex -eq $false -and $null -ne $Position) {
        if ($MultiSelect) {
            Return $MenuItems[$CurrentSelection]
        }
        else {
            Return $MenuItems[$Position]
        }
    }
    else {
        if ($MultiSelect) {
            Return $CurrentSelection
        }
        else {
            Return $Position
        }
    }
}