├── .gitattributes ├── .github └── FUNDING.yml ├── CNAME ├── ChooseMenuUsage.md ├── InteractiveMenu ├── InteractiveMenu.psd1 └── InteractiveMenu.psm1 ├── LICENSE ├── MultiMenuUsage.md ├── README.md ├── _config.yml ├── logo.png ├── sample-choosemenu.ps1 └── sample-multimenu.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | *.psd1 text eol=crlf -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: bibistroc 2 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | powershell-menu.gbarbu.eu -------------------------------------------------------------------------------- /ChooseMenuUsage.md: -------------------------------------------------------------------------------- 1 | # Interactive Choose Menu 2 | 3 | ## Demo 4 | 5 | [![asciicast](https://asciinema.org/a/wdIQTAFPqnsu36RECXv8MAXrH.svg)](https://asciinema.org/a/wdIQTAFPqnsu36RECXv8MAXrH) 6 | 7 | ## Install 8 | 9 | ```powershell 10 | Install-Module -Name InteractiveMenu 11 | ``` 12 | 13 | ## Prerequisites 14 | 15 | This module only works in Powershell version 5.0 and up. 16 | 17 | ## Usage 18 | 19 | Sample code can be found [here](sample-choosemenu) 20 | 21 | ### Using classes 22 | 23 | ```powershell 24 | # Import the module 25 | using module InteractiveMenu 26 | 27 | # Define the items for the menu 28 | $answerItems = @( 29 | [InteractiveMenuChooseMenuItem]::new("I choose good", "good", "Good info"), 30 | [InteractiveMenuChooseMenuItem]::new("I choose bad", "bad", "Bad info") 31 | ); 32 | 33 | # Define the question of the menu 34 | $question = "Choos between good and bad" 35 | 36 | # Instantiate new menu object 37 | $menu = [InteractiveMenuChooseMenu]::new($question, $answerItems); 38 | 39 | # [Optional] You can change the colors and the separator 40 | $options = @{ 41 | MenuInfoColor = [ConsoleColor]::DarkYellow 42 | QuestionColor = [ConsoleColor]::Magenta 43 | HelpColor = [ConsoleColor]::Cyan 44 | ErrorColor = [ConsoleColor]::DarkRed 45 | HighlightColor = [ConsoleColor]::DarkGreen 46 | OptionSeparator = " " 47 | } 48 | $menu.SetOptions($options) 49 | 50 | # Trigger the menu and receive the user answer 51 | $answer = $menu.GetAnswer() 52 | 53 | Write-Host "The user answered: $answer" 54 | ``` 55 | 56 | ### Using functions 57 | 58 | ```powershell 59 | # Import the module 60 | Import-Module InteractiveMenu 61 | 62 | # Define the items for the menu 63 | $answerItems = @( 64 | Get-InteractiveChooseMenuOption ` 65 | -Label "I choose good" ` 66 | -Value "good" ` 67 | -Info "Good info" 68 | Get-InteractiveChooseMenuOption ` 69 | -Label "I choose bad" ` 70 | -Value "bad" ` 71 | -Info "Bad info" 72 | ) 73 | 74 | # [Optional] You can change the colors and the symbols 75 | $options = @{ 76 | MenuInfoColor = [ConsoleColor]::DarkYellow 77 | QuestionColor = [ConsoleColor]::Magenta 78 | HelpColor = [ConsoleColor]::Cyan 79 | ErrorColor = [ConsoleColor]::DarkRed 80 | HighlightColor = [ConsoleColor]::DarkGreen 81 | OptionSeparator = " " 82 | } 83 | 84 | # Define the question of the menu 85 | $question = "Choos between good and bad" 86 | 87 | # Trigger the menu and receive the user answer 88 | # Note: the options parameter is optional 89 | $answer = Get-InteractiveMenuChooseUserSelection -Question $question -Answers $answerItems -Options $options 90 | ``` 91 | 92 | ## Refferences 93 | 94 | ### Class `InteractiveMenuChooseMenuItem` 95 | 96 | #### Constructors 97 | 98 | * `InteractiveMenuChooseMenuItem([string]$label, [string]$value, [string]$info)` 99 | 100 | #### Fields 101 | 102 | * **[Mandatory]** `[string]$Label` - The text that you want to show for your option 103 | * **[Mandatory]** `[string]$Value` - The value that you want returned if the user selects it 104 | * **[Mandatory]** `[string]$Info` - The information about the option. This information is displayed under the options 105 | 106 | ### Class `InteractiveMenuChooseMenu` 107 | 108 | #### Constructors 109 | 110 | * `InteractiveMenuChooseMenu([string]$question, [InteractiveMenuChooseMenuItem[]]$options)` 111 | 112 | #### Fields 113 | 114 | * **[Mandatory]** `[string]$question` - The question that you want to show above the options 115 | * **[Mandatory]** `[InteractiveMenuChooseMenuItem[]]$options` - The list of answers to display in the menu 116 | 117 | #### Methods 118 | 119 | * `[string] GetAnswer()` - Execute this method to trigger the menu and get the answer that the user selected 120 | * `[void] SetOptions([hashtable]$options)` - Execute this method to alter the options of the menu (more details in the [Options](#Options) section) 121 | 122 | ## Options 123 | 124 | * `MenuInfoColor` - Color of the item information. Default: `[ConsoleColor]::DarkYellow` 125 | * `QuestionColor` - Color of the question text. Default: `[ConsoleColor]::Magenta` 126 | * `HelpColor` - Color of the help items. Default: `[ConsoleColor]::Cyan` 127 | * `ErrorColor` - Color of the errors. Default: `[ConsoleColor]::DarkRed` 128 | * `HighlightColor` - Color of the current selected item. Default: `[ConsoleColor]::DarkGreen` 129 | * `OptionSeparator` - The separator between the answers. Default: "spacespacespacespacespacespace" (6 spaces) 130 | -------------------------------------------------------------------------------- /InteractiveMenu/InteractiveMenu.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'InteractiveMenu' 3 | # 4 | # Generated by: Gabriel Barbu 5 | # 6 | # Generated on: 20-Apr-20 7 | # 8 | 9 | @{ 10 | # Script module or binary module file associated with this manifest. 11 | RootModule = '.\InteractiveMenu.psm1' 12 | 13 | # Version number of this module. 14 | ModuleVersion = '0.5' 15 | 16 | # Supported PSEditions 17 | # CompatiblePSEditions = @() 18 | 19 | # ID used to uniquely identify this module 20 | GUID = 'e13c92f9-94c0-4e62-abb5-50e0f6ce5fab' 21 | 22 | # Author of this module 23 | Author = 'Gabriel Barbu' 24 | 25 | # Company or vendor of this module 26 | CompanyName = 'Unknown' 27 | 28 | # Copyright statement for this module 29 | Copyright = '(c) 2020 Gabriel Barbu. All rights reserved.' 30 | 31 | # Description of the functionality provided by this module 32 | Description = 'Powershell interactive menu' 33 | 34 | # Minimum version of the Windows PowerShell engine required by this module 35 | PowerShellVersion = '5.1' 36 | 37 | # Name of the Windows PowerShell host required by this module 38 | # PowerShellHostName = '' 39 | 40 | # Minimum version of the Windows PowerShell host required by this module 41 | # PowerShellHostVersion = '' 42 | 43 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 44 | # DotNetFrameworkVersion = '' 45 | 46 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 47 | # CLRVersion = '' 48 | 49 | # Processor architecture (None, X86, Amd64) required by this module 50 | # ProcessorArchitecture = '' 51 | 52 | # Modules that must be imported into the global environment prior to importing this module 53 | # RequiredModules = @() 54 | 55 | # Assemblies that must be loaded prior to importing this module 56 | # RequiredAssemblies = @() 57 | 58 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 59 | # ScriptsToProcess = @() 60 | 61 | # Type files (.ps1xml) to be loaded when importing this module 62 | # TypesToProcess = @() 63 | 64 | # Format files (.ps1xml) to be loaded when importing this module 65 | # FormatsToProcess = @() 66 | 67 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 68 | # NestedModules = @() 69 | 70 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 71 | FunctionsToExport = @('Get-InteractiveMultiMenuOption', 'Get-InteractiveMenuUserSelection', 'Get-InteractiveChooseMenuOption', 'Get-InteractiveMenuChooseUserSelection') 72 | 73 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 74 | CmdletsToExport = '*' 75 | 76 | # Variables to export from this module 77 | VariablesToExport = '*' 78 | 79 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 80 | AliasesToExport = '*' 81 | 82 | # DSC resources to export from this module 83 | # DscResourcesToExport = @() 84 | 85 | # List of all modules packaged with this module 86 | # ModuleList = @() 87 | 88 | # List of all files packaged with this module 89 | # FileList = @() 90 | 91 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 92 | PrivateData = @{ 93 | 94 | PSData = @{ 95 | # Tags applied to this module. These help with module discovery in online galleries. 96 | # Tags = @() 97 | 98 | # A URL to the license for this module. 99 | LicenseUri = 'https://raw.githubusercontent.com/bibistroc/powershell-interactive-menu/master/LICENSE' 100 | 101 | # A URL to the main website for this project. 102 | ProjectUri = 'https://powershell-menu.gbarbu.eu/' 103 | 104 | # A URL to an icon representing this module. 105 | IconUri = 'https://raw.githubusercontent.com/bibistroc/powershell-interactive-menu/master/logo.png' 106 | 107 | # ReleaseNotes of this module 108 | ReleaseNotes = 'Fix color -1 does not exist when background color is not set in IDE. Using VSCode on a Mac, with PowerShell Core.' 109 | 110 | } # End of PSData hashtable 111 | 112 | } #End of PrivateData hashtable 113 | 114 | # HelpInfo URI of this module 115 | # HelpInfoURI = '' 116 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 117 | # DefaultCommandPrefix = '' 118 | } -------------------------------------------------------------------------------- /InteractiveMenu/InteractiveMenu.psm1: -------------------------------------------------------------------------------- 1 | #Requires -Version 5 2 | 3 | class InteractiveMultiMenuItem { 4 | [ValidateNotNullOrEmpty()][object]$ItemInfo 5 | [ValidateNotNullOrEmpty()][string]$Label 6 | [ValidateNotNullOrEmpty()][bool]$Selected 7 | [ValidateNotNullOrEmpty()][int]$Order 8 | [bool]$Readonly 9 | [string]$Info 10 | [string]$Url 11 | 12 | InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [int]$order) { 13 | $this.Init($itemInfo, $label, $false, $order, $null, $null, $null); 14 | } 15 | 16 | InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order) { 17 | $this.Init($itemInfo, $label, $selected, $order, $null, $null, $null); 18 | } 19 | 20 | InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order, [bool]$readonly) { 21 | $this.Init($itemInfo, $label, $selected, $order, $readonly, $null, $null); 22 | } 23 | 24 | InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order, [bool]$readonly, [string]$info) { 25 | $this.Init($itemInfo, $label, $selected, $order, $readonly, $info, $null); 26 | } 27 | 28 | InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order, [bool]$readonly, [string]$info, [string]$url) { 29 | $this.Init($itemInfo, $label, $selected, $order, $readonly, $info, $url); 30 | } 31 | 32 | hidden Init([object]$itemInfo, [string]$label, [bool]$selected, [int]$order, [bool]$readonly, [string]$info, [string]$url) { 33 | $this.ItemInfo = $itemInfo 34 | $this.Label = $label 35 | $this.Selected = $selected 36 | $this.Order = $order 37 | $this.Readonly = $readonly 38 | $this.Info = $info 39 | $this.Url = $url 40 | } 41 | } 42 | 43 | class InteractiveMultiMenu { 44 | [ValidateNotNullOrEmpty()][string]$Header 45 | [ValidateNotNullOrEmpty()][InteractiveMultiMenuItem[]]$Items 46 | 47 | [ValidateNotNullOrEmpty()][ConsoleColor]$HeaderColor = [ConsoleColor]::Magenta 48 | [ValidateNotNullOrEmpty()][ConsoleColor]$HelpColor = [ConsoleColor]::Cyan 49 | [ValidateNotNullOrEmpty()][ConsoleColor]$CurrentItemColor = [ConsoleColor]::DarkGreen 50 | [ValidateNotNullOrEmpty()][ConsoleColor]$LinkColor = [ConsoleColor]::DarkCyan 51 | [ValidateNotNullOrEmpty()][ConsoleColor]$CurrentItemLinkColor = [ConsoleColor]::Black 52 | 53 | [ValidateNotNullOrEmpty()][string]$MenuDeselected = "[ ]" 54 | [ValidateNotNullOrEmpty()][string]$MenuSelected = "[x]" 55 | [ValidateNotNullOrEmpty()][string]$MenuCannotSelect = "[/]" 56 | [ValidateNotNullOrEmpty()][string]$MenuCannotDeselect = "[!]" 57 | [ValidateNotNullOrEmpty()][ConsoleColor]$MenuInfoColor = [ConsoleColor]::DarkYellow 58 | [ValidateNotNullOrEmpty()][ConsoleColor]$MenuErrorColor = [ConsoleColor]::DarkRed 59 | 60 | hidden [object[]]$SelectedItems = $null 61 | hidden [int]$CurrentIndex = 0 62 | hidden [string]$Error = $null 63 | hidden [bool]$Help = $false 64 | hidden [bool]$Info = $false 65 | 66 | InteractiveMultiMenu([string]$header, [InteractiveMultiMenuItem[]]$items) { 67 | $this.Header = $header 68 | $this.Items = $items | Sort-Object { $_.Order } 69 | } 70 | 71 | [object[]] GetSelections() { 72 | Clear-Host 73 | $shouldContinue = $true 74 | do { 75 | [Console]::CursorLeft = 0 76 | [Console]::CursorTop = 0 77 | if ($this.Help) { 78 | Clear-Host 79 | $this.ShowHelp() 80 | [Console]::ReadKey("NoEcho,IncludeKeyDown") 81 | $this.Help = $false 82 | Clear-Host 83 | continue 84 | } 85 | if ($this.Info) { 86 | Clear-Host 87 | $this.ShowCurrentItemInfo() 88 | [Console]::ReadKey("NoEcho,IncludeKeyDown") 89 | $this.Info = $false 90 | Clear-Host 91 | continue 92 | } 93 | 94 | Write-Host "$($this.Header)`n" -ForegroundColor $this.HeaderColor 95 | $this.Draw() 96 | $this.ShowUsage() 97 | 98 | $this.ShowErrors() 99 | 100 | $keyPress = [Console]::ReadKey("NoEcho,IncludeKeyDown") 101 | $shouldContinue = $this.ProcessKey($keyPress) 102 | } while ($shouldContinue) 103 | 104 | return $this.SelectedItems 105 | } 106 | 107 | [void] SetOptions([hashtable]$options) { 108 | foreach ($option in $options.GetEnumerator()) { 109 | if ($null -eq $this.$($option.Key)) { 110 | Write-Host "Invalid option key: $($option.Key)" 111 | } else { 112 | $this.$($option.Key) = $option.Value 113 | } 114 | } 115 | } 116 | 117 | hidden Draw() { 118 | $defaultForegroundColor = (get-host).ui.rawui.ForegroundColor 119 | $defaultBackgroundColor = (get-host).ui.rawui.BackgroundColor 120 | 121 | $i = 0 122 | $this.Items | ForEach-Object { 123 | $currentItem = $_ 124 | $color = $defaultForegroundColor 125 | $background = $defaultBackgroundColor 126 | $linkColor = $this.LinkColor 127 | $selectionStatus = $this.MenuDeselected 128 | if ($i -eq $this.CurrentIndex) { 129 | $background = $this.CurrentItemColor 130 | $linkColor = $this.CurrentItemLinkColor 131 | } 132 | if ($currentItem.Selected) { 133 | $selectionStatus = $this.MenuSelected 134 | } 135 | 136 | if ($currentItem.Readonly) { 137 | if ($currentItem.Selected) { 138 | $selectionStatus = $this.MenuCannotDeselect 139 | } else { 140 | $selectionStatus = $this.MenuCannotSelect 141 | } 142 | } 143 | 144 | Write-Host "$($selectionStatus) $($currentItem.Label)" -NoNewline -ForegroundColor $color -BackgroundColor $background 145 | if (-not [string]::IsNullOrEmpty($currentItem.Url)) { 146 | Write-Host " [$($currentItem.Url)]" -ForegroundColor $linkColor -BackgroundColor $background 147 | } else { 148 | Write-Host 149 | } 150 | $i++ 151 | } 152 | } 153 | 154 | hidden ShowCurrentItemInfo() { 155 | $selectedItem = $this.GetSelectedItem(); 156 | 157 | Write-Host "`n$($selectedItem.Info)" -ForegroundColor $this.MenuInfoColor 158 | Write-Host "`nPress any key to go back" -ForegroundColor $this.HeaderColor 159 | } 160 | 161 | hidden [InteractiveMultiMenuItem] GetSelectedItem() { 162 | return $this.Items[$this.CurrentIndex]; 163 | } 164 | 165 | hidden [bool] ProcessKey($keyPress) { 166 | switch ($keyPress.Key) { 167 | $([ConsoleKey]::DownArrow) { 168 | $this.CurrentIndex++ 169 | if ($this.CurrentIndex -ge $this.Items.Length) { 170 | $this.CurrentIndex = $this.Items.Length -1; 171 | } 172 | } 173 | $([ConsoleKey]::D2) { # this is only for powersession 174 | $this.CurrentIndex++ 175 | if ($this.CurrentIndex -ge $this.Items.Length) { 176 | $this.CurrentIndex = $this.Items.Length -1; 177 | } 178 | } 179 | $([ConsoleKey]::UpArrow) { 180 | $this.CurrentIndex-- 181 | if ($this.CurrentIndex -lt 0) { 182 | $this.CurrentIndex = 0; 183 | } 184 | } 185 | $([ConsoleKey]::D8) { # this is only for powersession 186 | $this.CurrentIndex-- 187 | if ($this.CurrentIndex -lt 0) { 188 | $this.CurrentIndex = 0; 189 | } 190 | } 191 | $([ConsoleKey]::Spacebar) { 192 | $selectedItem = $this.GetSelectedItem() 193 | if ($selectedItem.Readonly) { 194 | $this.Error = "$($selectedItem.Label) is marked as readonly and cannot be changed" 195 | } else { 196 | $selectedItem.Selected=!$selectedItem.Selected; 197 | } 198 | } 199 | $([ConsoleKey]::A) { 200 | $this.Items | ForEach-Object { 201 | if (-not $_.Readonly) { 202 | $_.Selected = $true 203 | } 204 | } 205 | } 206 | $([ConsoleKey]::N) { 207 | $this.Items | ForEach-Object { 208 | if (-not $_.Readonly) { 209 | $_.Selected = $false 210 | } 211 | } 212 | } 213 | $([ConsoleKey]::H) { 214 | $this.Help = $true 215 | } 216 | $([ConsoleKey]::O) { 217 | $this.OpenBrowser() 218 | } 219 | $([ConsoleKey]::I) { 220 | $selectedItem = $this.GetSelectedItem() 221 | if (-not [string]::IsNullOrEmpty($selectedItem.Info)) { 222 | $this.Info = $true 223 | } 224 | } 225 | $([ConsoleKey]::Enter) { 226 | $this.StoreState() 227 | return $false 228 | } 229 | $([ConsoleKey]::Escape) { 230 | return $false 231 | } 232 | Default { 233 | $this.Error = "Unkown key pressed: $_. Press 'h' for help." 234 | } 235 | } 236 | return $true 237 | } 238 | 239 | hidden OpenBrowser() { 240 | $selectedItem = $this.GetSelectedItem() 241 | if (-not [string]::IsNullOrEmpty($selectedItem.Url)) { 242 | Start-Process "$($selectedItem.Url)" 243 | } else { 244 | $this.Error = "$($selectedItem.Label) doesn't have an URL" 245 | } 246 | } 247 | 248 | hidden StoreState() { 249 | $this.SelectedItems = @() 250 | foreach ($item in $this.Items) { 251 | if ($item.Selected) { 252 | $this.SelectedItems += $item.ItemInfo 253 | } 254 | } 255 | } 256 | 257 | hidden ShowHelp() { 258 | Write-Host "Legend:" 259 | Write-Host -NoNewline $this.MenuDeselected -ForegroundColor $this.HelpColor 260 | Write-Host " - unchecked item" 261 | Write-Host -NoNewline $this.MenuSelected -ForegroundColor $this.HelpColor 262 | Write-Host " - checked item" 263 | Write-Host -NoNewline $this.MenuCannotDeselect -ForegroundColor $this.HelpColor 264 | Write-Host " - mandatory (cannot uncheck)" 265 | Write-Host -NoNewline $this.MenuCannotSelect -ForegroundColor $this.HelpColor 266 | Write-Host " - unavailable (cannot check)" 267 | 268 | Write-Host "Usage:" 269 | Write-Host -NoNewline "[SpaceBar]" -ForegroundColor $this.HelpColor 270 | Write-Host -NoNewline " check/uncheck, " 271 | Write-Host -NoNewline "[Enter]" -ForegroundColor $this.HelpColor 272 | Write-Host -NoNewline " continue, " 273 | Write-Host -NoNewline "[Esc]" -ForegroundColor $this.HelpColor 274 | Write-Host " exit" 275 | Write-Host -NoNewline "[Up] [Down]" -ForegroundColor $this.HelpColor 276 | Write-Host -NoNewline " navigate, " 277 | Write-Host -NoNewline "[A]" -ForegroundColor $this.HelpColor 278 | Write-Host -NoNewline " select all, " 279 | Write-Host -NoNewline "[N]" -ForegroundColor $this.HelpColor 280 | Write-Host " deselect all" 281 | Write-Host -NoNewline "[O]" -ForegroundColor $this.HelpColor 282 | Write-Host " open url (if any)" 283 | Write-Host -NoNewline "[I]" -ForegroundColor $this.HelpColor 284 | Write-Host " show information about selected item (if any)" 285 | 286 | Write-Host "`nPress any key to go back" -ForegroundColor $this.HeaderColor 287 | } 288 | 289 | hidden ShowUsage() { 290 | Write-Host "`nPress [h] for help." -ForegroundColor $this.HelpColor 291 | } 292 | 293 | hidden ShowErrors() { 294 | $bufferFill = " " 295 | if (-not [string]::IsNullOrEmpty($this.Error)) { 296 | Write-Host "$($this.Error)$bufferFill" -ForegroundColor $this.MenuErrorColor 297 | $this.Error = $null 298 | } else { 299 | Write-Host $bufferFill 300 | } 301 | } 302 | } 303 | 304 | class InteractiveMenuChooseMenuItem { 305 | [ValidateNotNullOrEmpty()][string]$Label 306 | [ValidateNotNullOrEmpty()][string]$Value 307 | [ValidateNotNullOrEmpty()][string]$Info 308 | 309 | InteractiveMenuChooseMenuItem([string]$label, [string]$value, [string]$info) { 310 | $this.Label = $label 311 | $this.Value = $value 312 | $this.Info = $Info 313 | } 314 | } 315 | 316 | class InteractiveMenuChooseMenu { 317 | [ValidateNotNullOrEmpty()][string]$Question 318 | [ValidateNotNullOrEmpty()][InteractiveMenuChooseMenuItem[]]$Options 319 | 320 | [ValidateNotNullOrEmpty()][ConsoleColor]$MenuInfoColor = [ConsoleColor]::DarkYellow 321 | [ValidateNotNullOrEmpty()][ConsoleColor]$QuestionColor = [ConsoleColor]::Magenta 322 | [ValidateNotNullOrEmpty()][ConsoleColor]$HelpColor = [ConsoleColor]::Cyan 323 | [ValidateNotNullOrEmpty()][ConsoleColor]$ErrorColor = [ConsoleColor]::DarkRed 324 | [ValidateNotNullOrEmpty()][ConsoleColor]$HighlightColor = [ConsoleColor]::DarkGreen 325 | [ValidateNotNullOrEmpty()][string]$OptionSeparator = " " 326 | 327 | hidden [int]$CurrentIndex = 0 328 | hidden [InteractiveMenuChooseMenuItem]$SelectedOption = $null 329 | hidden [bool]$Help = $false 330 | hidden [string]$Error = $null 331 | 332 | InteractiveMenuChooseMenu([string]$question, [InteractiveMenuChooseMenuItem[]]$options) { 333 | $this.Question = $question 334 | $this.Options = $options 335 | } 336 | 337 | [string] GetAnswer() { 338 | $shouldContinue = $true 339 | do { 340 | Clear-Host 341 | if ($this.Help) { 342 | $this.ShowHelp() 343 | [Console]::ReadKey("NoEcho,IncludeKeyDown") 344 | continue 345 | } 346 | 347 | Write-Host "$($this.Question)`n" -ForegroundColor $this.QuestionColor 348 | $this.Draw() 349 | $this.ShowCurrentItemInfo() 350 | $this.ShowUsage() 351 | $this.ShowErrors() 352 | 353 | $keyPress = [Console]::ReadKey("NoEcho,IncludeKeyDown") 354 | $shouldContinue = $this.ProcessKey($keyPress) 355 | } while ($shouldContinue) 356 | 357 | if ($null -eq $this.SelectedOption) { 358 | return $null 359 | } 360 | return $this.SelectedOption.Value 361 | } 362 | 363 | [void] SetOptions([hashtable]$options) { 364 | foreach ($option in $options.GetEnumerator()) { 365 | if ($null -eq $this.$($option.Key)) { 366 | Write-Host "Invalid option key: $($option.Key)" 367 | } else { 368 | $this.$($option.Key) = $option.Value 369 | } 370 | } 371 | } 372 | 373 | hidden Draw() { 374 | $defaultForegroundColor = (get-host).ui.rawui.ForegroundColor 375 | $defaultBackgroundColor = (get-host).ui.rawui.BackgroundColor 376 | 377 | $i = 0 378 | $this.options | ForEach-Object { 379 | $foregroundColor = $defaultForegroundColor 380 | $backgroundColor = $defaultBackgroundColor 381 | if ($i -eq $this.CurrentIndex) { 382 | $backgroundColor = $this.HighlightColor 383 | } 384 | # Fix if foreground or background color is not valid or set in IDE 385 | if (![Enum]::IsDefined([ConsoleColor], $foregroundColor)) { 386 | # If foreground color is not a valid color, set it to a default color. 387 | $foregroundColor = [ConsoleColor]::White 388 | } 389 | if (![Enum]::IsDefined([ConsoleColor], $backgroundColor)) { 390 | # If background color is not a valid color, set it to a default color. 391 | $backgroundColor = [ConsoleColor]::Black 392 | } 393 | Write-Host " $($_.Label) " -NoNewline -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor 394 | Write-Host $this.OptionSeparator -NoNewline 395 | $i++ 396 | } 397 | Write-Host 398 | } 399 | 400 | hidden ShowCurrentItemInfo() { 401 | $selectedItem = $this.Options[$this.CurrentIndex]; 402 | if (-not [string]::IsNullOrEmpty($selectedItem.Info)) { 403 | Write-Host "`n$($selectedItem.Info)" -ForegroundColor $this.MenuInfoColor 404 | } 405 | } 406 | 407 | hidden [bool] ProcessKey($keyPress) { 408 | switch ($keyPress.Key) { 409 | $([ConsoleKey]::RightArrow) { 410 | $this.CurrentIndex++ 411 | if ($this.CurrentIndex -ge $this.Options.Length) { 412 | $this.CurrentIndex = $this.Options.Length -1; 413 | } 414 | } 415 | $([ConsoleKey]::D6) { # this is only for powersession 416 | $this.CurrentIndex++ 417 | if ($this.CurrentIndex -ge $this.Options.Length) { 418 | $this.CurrentIndex = $this.Options.Length -1; 419 | } 420 | } 421 | $([ConsoleKey]::LeftArrow) { 422 | $this.CurrentIndex-- 423 | if ($this.CurrentIndex -lt 0) { 424 | $this.CurrentIndex = 0; 425 | } 426 | } 427 | $([ConsoleKey]::D4) { # this is only for powersession 428 | $this.CurrentIndex-- 429 | if ($this.CurrentIndex -lt 0) { 430 | $this.CurrentIndex = 0; 431 | } 432 | } 433 | $([ConsoleKey]::H) { 434 | $this.Help = $true 435 | } 436 | $([ConsoleKey]::Enter) { 437 | $this.StoreState() 438 | return $false 439 | } 440 | $([ConsoleKey]::Escape) { 441 | return $false 442 | } 443 | Default { 444 | $this.Error = "Unkown key pressed: $_. Press 'h' for help." 445 | } 446 | } 447 | return $true 448 | } 449 | 450 | hidden StoreState() { 451 | $this.SelectedOption = $this.Options[$this.CurrentIndex] 452 | } 453 | 454 | hidden ShowUsage() { 455 | Write-Host "`nPress [h] for help." -ForegroundColor $this.HelpColor 456 | } 457 | 458 | hidden ShowErrors() { 459 | $bufferFill = " " 460 | if (-not [string]::IsNullOrEmpty($this.Error)) { 461 | Write-Host "$($this.Error)$bufferFill" -ForegroundColor $this.ErrorColor 462 | $this.Error = $null 463 | } else { 464 | Write-Host $bufferFill 465 | } 466 | } 467 | 468 | hidden ShowHelp() { 469 | Write-Host "Usage:" 470 | Write-Host -NoNewline "[Enter]" -ForegroundColor $this.HelpColor 471 | Write-Host -NoNewline " continue, " 472 | Write-Host -NoNewline "[Esc]" -ForegroundColor $this.HelpColor 473 | Write-Host " exit" 474 | Write-Host -NoNewline "[Lef] [Right]" -ForegroundColor $this.HelpColor 475 | Write-Host " navigate, " 476 | 477 | Write-Host "`nPress any key to go back" -ForegroundColor $this.QuestionColor 478 | $this.Help = $false 479 | } 480 | } 481 | 482 | function Get-InteractiveMultiMenuOption { 483 | param( 484 | [Parameter(Mandatory)][object]$Item, 485 | [Parameter(Mandatory)][string]$Label, 486 | [Parameter(Mandatory)][int]$Order, 487 | [Parameter()][switch]$Selected, 488 | [Parameter()][switch]$Readonly, 489 | [Parameter()][string]$Info, 490 | [Parameter()][string]$Url 491 | ) 492 | [InteractiveMultiMenuItem]::New($Item, $Label, $Selected.IsPresent, $Order, $Readonly.IsPresent, $Info, $Url) 493 | } 494 | 495 | function Get-InteractiveMenuUserSelection { 496 | param( 497 | [Parameter(Mandatory)][string]$Header, 498 | [Parameter(Mandatory)][object[]]$Items, 499 | [Parameter()][hashtable]$Options 500 | ) 501 | $menu = [InteractiveMultiMenu]::New($Header, $Items) 502 | if ($null -ne $Options) { 503 | $menu.SetOptions($Options); 504 | } 505 | return $menu.GetSelections() 506 | } 507 | 508 | function Get-InteractiveChooseMenuOption() { 509 | param( 510 | [Parameter(Mandatory)][string]$Label, 511 | [Parameter(Mandatory)][string]$Value, 512 | [Parameter(Mandatory)][string]$Info 513 | ) 514 | 515 | [InteractiveMenuChooseMenuItem]::new($Label, $Value, $Info) 516 | } 517 | 518 | function Get-InteractiveMenuChooseUserSelection { 519 | param( 520 | [Parameter(Mandatory)][string]$Question, 521 | [Parameter(Mandatory)][object[]]$Answers, 522 | [Parameter()][hashtable]$Options 523 | ) 524 | 525 | $menu = [InteractiveMenuChooseMenu]::new($Question, $Answers) 526 | if ($null -ne $Options) { 527 | $menu.SetOptions($Options); 528 | } 529 | return $menu.GetAnswer() 530 | } 531 | 532 | Export-ModuleMember -Function Get-InteractiveMultiMenuOption,Get-InteractiveMenuUserSelection,Get-InteractiveChooseMenuOption,Get-InteractiveMenuChooseUserSelection 533 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gabriel Barbu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MultiMenuUsage.md: -------------------------------------------------------------------------------- 1 | # Interactive Multi Menu 2 | 3 | ## Demo 4 | 5 | [![asciicast](https://asciinema.org/a/IbuA6vFBCcN9CQImZYLsHIXUu.svg)](https://asciinema.org/a/IbuA6vFBCcN9CQImZYLsHIXUu) 6 | 7 | ## Install 8 | 9 | ```powershell 10 | Install-Module -Name InteractiveMenu 11 | ``` 12 | 13 | ## Prerequisites 14 | 15 | This module only works in Powershell version 5.0 and up. 16 | 17 | ## Usage 18 | 19 | Sample code can be found [here](sample-multimenu.ps1) 20 | 21 | ### Using classes 22 | 23 | ```powershell 24 | # Import the module 25 | using module InteractiveMenu 26 | 27 | # Define the items for the menu 28 | # Note: the url and info are optional 29 | $menuItems = @( 30 | [InteractiveMultiMenuItem]::new("option1", "First Option", $true, 0, $false, "First option info", "https://example.com/") 31 | [InteractiveMultiMenuItem]::new("Option 2", "Second Option", $true, 1, $false, "Second option info", "https://example.com/") 32 | ) 33 | 34 | # Define the header of the menu 35 | $header = "Choose your options" 36 | 37 | # Instantiate new menu object 38 | $menu = [InteractiveMultiMenu]::new($header, $menuItems); 39 | 40 | # [Optional] You can change the colors and the symbols 41 | $options = @{ 42 | HeaderColor = [ConsoleColor]::Magenta; 43 | HelpColor = [ConsoleColor]::Cyan; 44 | CurrentItemColor = [ConsoleColor]::DarkGreen; 45 | LinkColor = [ConsoleColor]::DarkCyan; 46 | CurrentItemLinkColor = [ConsoleColor]::Black; 47 | MenuDeselected = "[ ]"; 48 | MenuSelected = "[x]"; 49 | MenuCannotSelect = "[/]"; 50 | MenuCannotDeselect = "[!]"; 51 | MenuInfoColor = [ConsoleColor]::DarkYellow; 52 | MenuErrorColor = [ConsoleColor]::DarkRed; 53 | } 54 | $menu.SetOptions($options) 55 | 56 | # Trigger the menu and receive the user selections 57 | $selectedItems = $menu.GetSelections() 58 | 59 | foreach ($item in $selectedItem) { 60 | Write-Host $item 61 | } 62 | ``` 63 | 64 | ### Using functions 65 | 66 | ```powershell 67 | # Import the module 68 | Import-Module InteractiveMenu 69 | 70 | # Define the items for the menu 71 | # Note: the url, info, selected and readonly parameters are optional 72 | $menuItems = @( 73 | Get-InteractiveMultiMenuOption ` 74 | -Item "option1" ` 75 | -Label "First Option" ` 76 | -Order 0 ` 77 | -Info "First option info" ` 78 | -Url "https://example.com" 79 | Get-InteractiveMultiMenuOption ` 80 | -Item "option2" ` 81 | -Label "Second Option" ` 82 | -Order 1 ` 83 | -Info "Second option info" ` 84 | -Url "https://example.com" ` 85 | -Selected ` 86 | -Readonly 87 | ) 88 | 89 | # [Optional] You can change the colors and the symbols 90 | $options = @{ 91 | HeaderColor = [ConsoleColor]::Magenta; 92 | HelpColor = [ConsoleColor]::Cyan; 93 | CurrentItemColor = [ConsoleColor]::DarkGreen; 94 | LinkColor = [ConsoleColor]::DarkCyan; 95 | CurrentItemLinkColor = [ConsoleColor]::Black; 96 | MenuDeselected = "[ ]"; 97 | MenuSelected = "[x]"; 98 | MenuCannotSelect = "[/]"; 99 | MenuCannotDeselect = "[!]"; 100 | MenuInfoColor = [ConsoleColor]::DarkYellow; 101 | MenuErrorColor = [ConsoleColor]::DarkRed; 102 | } 103 | 104 | # Define the header of the menu 105 | $header = "Choose your options" 106 | 107 | # Trigger the menu and receive the user selections 108 | # Note: the options parameter is optional 109 | $selectedOptions = Get-InteractiveMenuUserSelection -Header $header -Items $menuItems -Options $options 110 | ``` 111 | 112 | ## Refferences 113 | 114 | ### Class `InteractiveMultiMenuItem` 115 | 116 | #### Constructors 117 | 118 | * `InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [int]$order)` 119 | * `InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order)` 120 | * `InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order, [bool]$readonly)` 121 | * `InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order, [bool]$readonly, [string]$info)` 122 | * `InteractiveMultiMenuItem([object]$itemInfo, [string]$label, [bool]$selected, [int]$order, [bool]$readonly, [string]$info, [string]$url)` 123 | 124 | #### Fields 125 | 126 | * **[Mandatory]** `[object]$ItemInfo` - The item that you want returned if the user selects it 127 | * **[Mandatory]** `[string]$Label` - The text that you want to show for your option 128 | * **[Mandatory]** `[int]$Order` - The order of the item in the list. You can have multiple items on the same order. The script will order them. 129 | * **[Optional]** `[bool]$Selected` - If the option is selected or not by default. Possible values: `$true` or `$false` 130 | * **[Optional]** `[bool]$Readonly` - If the option is readonly (cannot be changed). Possible values: `$true` or `$false` 131 | * **[Optional]** `[string]$Info` - The information about the item. This information is visible if the user press `I`. 132 | * **[Optional]** `[string]$Url` - The URL of the item. A browser window will open on this URL if the user press `O`. 133 | 134 | ### Class `InteractiveMultiMenu` 135 | 136 | #### Constructors 137 | 138 | * `InteractiveMultiMenu([string]$header, [InteractiveMultiMenuItem[]]$items)` 139 | 140 | #### Fields 141 | 142 | * **[Mandatory]** `[string]$header` - The text that you want to display above the menu 143 | * **[Mandatory]** `[InteractiveMultiMenuItem[]]$items` - The list of items to display in the menu 144 | 145 | #### Methods 146 | 147 | * `[object[]] GetSelections()` - Execute this method to trigger the menu and get the objects that the user selected 148 | * `[void] SetOptions([hashtable]$options)` - Execute this method to alter the options of the menu (more details in the [Options](#Options) section) 149 | 150 | ## Options 151 | 152 | * `HeaderColor` - Color of the header. Default: `[ConsoleColor]::Magenta;` 153 | * `HelpColor` - Color of the help items. Default: `[ConsoleColor]::Cyan;` 154 | * `CurrentItemColor` - Color of the current selected item. Default: `[ConsoleColor]::DarkGreen;` 155 | * `LinkColor` - Color of the links. Default: `[ConsoleColor]::DarkCyan;` 156 | * `CurrentItemLinkColor` - Color of the current selected item link. Default: `[ConsoleColor]::Black;` 157 | * `MenuDeselected` - Sympol for unselected item. Default: `"[ ]";` 158 | * `MenuSelected` - Symbol for selected item. Default: `"[x]";` 159 | * `MenuCannotSelect` - Symbol for the item that cannot be selected. Default: `"[/]";` 160 | * `MenuCannotDeselect` - Sympol for the item that cannot be deselected. Default: `"[!]";` 161 | * `MenuInfoColor` - Color of the item information. Default: `[ConsoleColor]::DarkYellow;` 162 | * `MenuErrorColor` - Color of the errors. Default: `[ConsoleColor]::DarkRed;` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Powershell Interactive Menu 2 | 3 | > Note: this project is in development now. New versions will be available soon 4 | 5 | ## Description 6 | 7 | The scope of this module is to provide some interactive menus to be used in powershell. 8 | 9 | ### Interactive multi menu 10 | 11 | [![asciicast](https://asciinema.org/a/IbuA6vFBCcN9CQImZYLsHIXUu.svg)](https://asciinema.org/a/IbuA6vFBCcN9CQImZYLsHIXUu) 12 | 13 | ### Interactive choose menu 14 | 15 | [![asciicast](https://asciinema.org/a/wdIQTAFPqnsu36RECXv8MAXrH.svg)](https://asciinema.org/a/wdIQTAFPqnsu36RECXv8MAXrH) 16 | 17 | ## Install 18 | 19 | ```powershell 20 | Install-Module -Name InteractiveMenu 21 | ``` 22 | 23 | Or manual download from [PowershellGallery](https://www.powershellgallery.com/packages/InteractiveMenu). 24 | 25 | Or clone this repository. 26 | 27 | ## Usage 28 | 29 | Usage for [MultiMenu](MultiMenuUsage.md). 30 | 31 | Usage for [ChooseMenu](ChooseMenuUsage.md). -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal 2 | logo: "logo.png" 3 | show_downloads: true 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bibistroc/powershell-interactive-menu/be7a4305af60c157718a43a23fa6b04b54ded84f/logo.png -------------------------------------------------------------------------------- /sample-choosemenu.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\InteractiveMenu\InteractiveMenu.psd1 2 | 3 | # Choose menu answers 4 | $answers = @( 5 | Get-InteractiveChooseMenuOption ` 6 | -Label "Option 1" ` 7 | -Value "1" ` 8 | -Info "This is the info for option 1" 9 | Get-InteractiveChooseMenuOption ` 10 | -Label "Option 2" ` 11 | -Value "2" ` 12 | -Info "This is the info for option 2" 13 | ) 14 | 15 | $options = @{ 16 | MenuInfoColor = [ConsoleColor]::DarkYellow; 17 | QuestionColor = [ConsoleColor]::Magenta; 18 | HelpColor = [ConsoleColor]::Cyan; 19 | ErrorColor = [ConsoleColor]::DarkRed; 20 | HighlightColor = [ConsoleColor]::DarkGreen; 21 | OptionSeparator = " "; 22 | } 23 | 24 | $answer = Get-InteractiveMenuChooseUserSelection -Question "Sample Question" -Answers $answers -Options $options 25 | 26 | Write-Host "Answer: $answer" 27 | 28 | Remove-Module InteractiveMenu -------------------------------------------------------------------------------- /sample-multimenu.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\InteractiveMenu\InteractiveMenu.psd1 2 | 3 | # Multiple selection items 4 | $multiMenuOptions = @( 5 | Get-InteractiveMultiMenuOption ` 6 | -Item "Sample option deselected" ` 7 | -Label "Sample option deselected" ` 8 | -Order 0 ` 9 | -Info "This is some info" ` 10 | -Url "https://google.com" 11 | Get-InteractiveMultiMenuOption ` 12 | -Item "Sample option selected" ` 13 | -Label "Sample option selected" ` 14 | -Order 1 ` 15 | -Info "This is some info" ` 16 | -Url "https://google.com" ` 17 | -Selected 18 | Get-InteractiveMultiMenuOption ` 19 | -Item "Sample option mandatory" ` 20 | -Label "Sample option mandatory" ` 21 | -Order 3 ` 22 | -Info "This is some info" ` 23 | -Url "https://google.com" ` 24 | -Selected ` 25 | -Readonly 26 | Get-InteractiveMultiMenuOption ` 27 | -Item "Sample option unavailable" ` 28 | -Label "Sample option unavailable" ` 29 | -Order 4 ` 30 | -Info "This is some info" ` 31 | -Url "https://google.com" ` 32 | -Readonly 33 | Get-InteractiveMultiMenuOption ` 34 | -Item "Sample option without info" ` 35 | -Label "Sample option without info" ` 36 | -Order 5 ` 37 | -Url "https://google.com" 38 | Get-InteractiveMultiMenuOption ` 39 | -Item "Sample option without URL" ` 40 | -Label "Sample option without URL" ` 41 | -Order 6 ` 42 | -Info "This is some info" 43 | ) 44 | 45 | $options = @{ 46 | HeaderColor = [ConsoleColor]::Magenta; 47 | HelpColor = [ConsoleColor]::Cyan; 48 | CurrentItemColor = [ConsoleColor]::DarkGreen; 49 | LinkColor = [ConsoleColor]::DarkCyan; 50 | CurrentItemLinkColor = [ConsoleColor]::Black; 51 | MenuDeselected = "[ ]"; 52 | MenuSelected = "[x]"; 53 | MenuCannotSelect = "[/]"; 54 | MenuCannotDeselect = "[!]"; 55 | MenuInfoColor = [ConsoleColor]::DarkYellow; 56 | MenuErrorColor = [ConsoleColor]::DarkRed; 57 | } 58 | 59 | $header = "Demo of the multi-selection menu" 60 | 61 | $selectedOptions = Get-InteractiveMenuUserSelection -Header $header -Items $multiMenuOptions -Options $options 62 | 63 | $selectedOptions | Format-List 64 | 65 | Remove-Module InteractiveMenu --------------------------------------------------------------------------------