source/Private/InteractiveMenuHelpers.ps1
|
# Interactive Menu Helper Functions # Provides error handling, retry logic, and operation management for the interactive menu function Test-PowerShellISE { return $Host.Name -eq "Windows PowerShell ISE Host" } function Invoke-RJOperationWithRetry { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification='This function is intended for interactive use')] [CmdletBinding()] param( [Parameter(Mandatory)] [scriptblock]$Operation, [Parameter(Mandatory)] [string]$OperationName, [int]$MaxRetries = 3, [int]$RetryDelaySeconds = 2, [switch]$ShowProgress ) $attempt = 0 $lastError = $null while ($attempt -le $MaxRetries) { try { Write-Verbose "Executing $OperationName (Attempt $($attempt + 1)/$($MaxRetries + 1))" if ($ShowProgress -and $attempt -gt 0) { Write-Host "Retry attempt $attempt of $MaxRetries..." -ForegroundColor Yellow } # Execute the operation $result = & $Operation # Success - return the result Write-Verbose "$OperationName completed successfully" return $result } catch { $lastError = $_ $attempt++ # Check if this is an authentication error (don't retry) if ($_.Exception.Message -match 'authentication|unauthorized|forbidden|401|403') { Write-Host "Authentication error during $OperationName`: $($_.Exception.Message)" -ForegroundColor Red throw $_ } # Check if we've exceeded max retries if ($attempt -gt $MaxRetries) { Write-Host "Failed after $MaxRetries retries: $OperationName" -ForegroundColor Red Write-Host "Last error: $($lastError.Exception.Message)" -ForegroundColor DarkRed # Offer manual retry option Write-Host "" Write-Host "Would you like to retry manually? (Y/N): " -ForegroundColor Yellow -NoNewline $response = Read-Host if ($response -eq 'Y' -or $response -eq 'y') { $attempt = 0 # Reset attempts for manual retry continue } throw $lastError } # Calculate backoff delay $delay = $RetryDelaySeconds * [Math]::Pow(2, $attempt - 1) Write-Host "Operation failed: $($_.Exception.Message)" -ForegroundColor Yellow Write-Host "Retrying in $delay seconds..." -ForegroundColor Gray Start-Sleep -Seconds $delay } } } function Show-RJGenericMenu { param( [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [string]$Prompt, [Parameter(Mandatory)] [PSCustomObject[]]$Options, [switch]$AllowEscape, [string[]]$ExtraInstructions, [string]$EscapePrompt = "Press Esc to return to main menu" ) $selectedIndex = 0 $key = $null while ($key -ne 13) { # 13 = Enter key Clear-Host Write-Host $Title -ForegroundColor White Write-Host ("-" * 50) -ForegroundColor DarkGray Write-Host "" Write-Host $Prompt -ForegroundColor White Write-Host "" for ($i = 0; $i -lt $Options.Count; $i++) { if ($i -eq $selectedIndex) { Write-Host " > $($Options[$i].Display)" -ForegroundColor White -NoNewline Write-Host " - $($Options[$i].Description)" -ForegroundColor Gray } else { Write-Host " $($Options[$i].Display)" -ForegroundColor DarkGray -NoNewline Write-Host " - $($Options[$i].Description)" -ForegroundColor DarkGray } } Write-Host "" Write-Host ("-" * 50) -ForegroundColor DarkGray # Build instruction text $maxOption = $Options.Count $instructionText = "Use arrow keys to navigate, Enter to select, or press 1-$maxOption" Write-Host $instructionText -ForegroundColor DarkGray if ($AllowEscape) { Write-Host $EscapePrompt -ForegroundColor DarkGray } if ($ExtraInstructions) { foreach ($instruction in $ExtraInstructions) { Write-Host $instruction -ForegroundColor DarkGray } } $keyInput = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $key = $keyInput.VirtualKeyCode switch ($key) { 38 { # Up arrow if ($selectedIndex -gt 0) { $selectedIndex-- } } 40 { # Down arrow if ($selectedIndex -lt ($Options.Count - 1)) { $selectedIndex++ } } 27 { # Escape key if ($AllowEscape) { return $null } } default { # Handle number keys (49-57 = keys 1-9) $numberKey = $key - 48 if ($numberKey -ge 1 -and $numberKey -le $Options.Count) { $selectedIndex = $numberKey - 1 $key = 13 # Trigger selection } } } } return $Options[$selectedIndex].Key } function Show-RJGenericMultiSelectMenu { param( [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [string]$Prompt, [Parameter(Mandatory)] [PSCustomObject[]]$Items, [string[]]$DefaultSelected = @(), [switch]$NoClearHost, [string]$SelectedIndicator = "X", [string]$SelectedColor = "White", [string]$EscapePrompt = "Press ESC to return to main menu" ) # Initialize selections $selections = @{} foreach ($item in $Items) { $selections[$item.Name] = $DefaultSelected -contains $item.Name } $itemNames = $Items | ForEach-Object { $_.Name } $selectedIndex = 0 $key = $null while ($key -ne 13) { # 13 = Enter key if (-not $NoClearHost) { Clear-Host } Write-Host $Title -ForegroundColor White Write-Host ("-" * 50) -ForegroundColor DarkGray Write-Host "" Write-Host $Prompt -ForegroundColor White Write-Host "" for ($i = 0; $i -lt $itemNames.Count; $i++) { $itemName = $itemNames[$i] $item = $Items | Where-Object { $_.Name -eq $itemName } if ($i -eq $selectedIndex) { Write-Host " > " -NoNewline -ForegroundColor White } else { Write-Host " " -NoNewline } if ($selections[$itemName]) { Write-Host "[$SelectedIndicator] " -NoNewline -ForegroundColor $SelectedColor } else { Write-Host "[ ] " -NoNewline -ForegroundColor DarkGray } if ($i -eq $selectedIndex) { Write-Host "$itemName" -ForegroundColor White -NoNewline } else { Write-Host "$itemName" -ForegroundColor Gray -NoNewline } Write-Host " - $($item.Description)" -ForegroundColor DarkGray } Write-Host "" Write-Host ("-" * 50) -ForegroundColor DarkGray Write-Host "Space: Toggle | A: All | N: None | Enter: Continue | $EscapePrompt" -ForegroundColor DarkGray $keyInput = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $key = $keyInput.VirtualKeyCode switch ($key) { 38 { # Up arrow if ($selectedIndex -gt 0) { $selectedIndex-- } } 40 { # Down arrow if ($selectedIndex -lt ($itemNames.Count - 1)) { $selectedIndex++ } } 32 { # Space bar $currentItem = $itemNames[$selectedIndex] $selections[$currentItem] = -not $selections[$currentItem] } 65 { # 'A' key - Select all foreach ($itemName in $itemNames) { $selections[$itemName] = $true } } 78 { # 'N' key - Select none foreach ($itemName in $itemNames) { $selections[$itemName] = $false } } 27 { # Escape key return $null } } } # Return selected items $selectedItems = @() foreach ($itemName in $itemNames) { if ($selections[$itemName]) { $selectedItems += $itemName } } return $selectedItems } |