├── .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 | [](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 | [](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 | [](https://asciinema.org/a/IbuA6vFBCcN9CQImZYLsHIXUu)
12 |
13 | ### Interactive choose menu
14 |
15 | [](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
--------------------------------------------------------------------------------