private/output/Add-ResultActionClickHandlers.ps1
|
function Add-ResultActionClickHandlers { <# .SYNOPSIS Wires up click handlers for ResultAction buttons with async execution. These buttons appear in Show-UIOutput for relevent results. #> [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$Captures, [Parameter(Mandatory)] [System.Collections.IList]$ActionButtons, [System.Windows.Controls.Primitives.Popup]$DropdownPopup ) # Unpack captures for closure access # There may be a better/cleaner way to do this, but this works. # It is neccessary because the closure created for the click handler # needs to capture the current values of these variables, not references. $capturedWindow = $Captures.Window $capturedState = $Captures.State $capturedDataGrid = $Captures.DataGrid $capturedResultsBorder = $Captures.ResultsBorder $capturedConsoleTab = $Captures.ConsoleTab $capturedConsoleParagraph = $Captures.ConsoleParagraph $capturedConsoleColorMap = $Captures.ConsoleColorMap $capturedAppendConsoleText = $Captures.AppendConsoleText $capturedAppendState = $Captures.AppendState $capturedErrorsTab = $Captures.ErrorsTab $capturedErrorsList = $Captures.ErrorsList $capturedWarningsTab = $Captures.WarningsTab $capturedWarningsTextBox = $Captures.WarningsTextBox $capturedWarningCount = $Captures.WarningCount $capturedProgressPanel = $Captures.ProgressPanel $capturedProgressBar = $Captures.ProgressBar $capturedProgressLabel = $Captures.ProgressLabel $capturedHeaderTitle = $Captures.HeaderTitle $capturedTitle = $Captures.Title $capturedTabControl = $Captures.TabControl $capturedTabNotifications = $Captures.TabNotifications $capturedErrorCount = $Captures.ErrorCount $capturedStatusSpinner = $Captures.StatusSpinner $capturedStatusSuccess = $Captures.StatusSuccess $capturedStatusWarning = $Captures.StatusWarning $capturedStatusIndicator = $Captures.StatusIndicator $capturedVarValues = $Captures.VarValues $capturedFuncDefs = $Captures.FuncDefs $capturedModules = $Captures.Modules $capturedDropdownPopup = $DropdownPopup # Capture the Input Dialog command to ensure it's available inside the closure $capturedShowInput = Get-Command Show-UiInputDialog -ErrorAction SilentlyContinue foreach ($actionButton in $ActionButtons) { $actionButton.Add_Click({ try { # Close dropdown popup if this is a menu item click if ($capturedDropdownPopup -and $capturedDropdownPopup.IsOpen) { $capturedDropdownPopup.IsOpen = $false } $def = $this.Tag if ($capturedState.DebugEnabled) { [Console]::WriteLine("[DEBUG] ResultAction clicked: '$($def.Text)'") } # Get the correct DataGrid - might be in a sub-TabControl for multi-type results $activeGrid = $capturedDataGrid if ($capturedResultsBorder.Child -is [System.Windows.Controls.TabControl]) { $subTabControl = $capturedResultsBorder.Child $selectedTab = $subTabControl.SelectedItem if ($selectedTab -and $selectedTab.Content -is [System.Windows.Controls.DataGrid]) { $activeGrid = $selectedTab.Content } } $selected = @($activeGrid.SelectedItems) if ($capturedState.DebugEnabled) { [Console]::WriteLine("[DEBUG] Selected items: $($selected.Count)") } if ($selected.Count -eq 0) { Show-UiMessageDialog -Message "Please select one or more items first." -Title "No Selection" -Icon "Info" return } # Show confirmation if required if ($def.Confirm) { $msg = $def.Confirm -f $selected.Count if ($capturedState.DebugEnabled) { [Console]::WriteLine("[DEBUG] Showing confirmation: $msg") } $result = Show-UiConfirmDialog -Title "Confirm Action" -Message $msg -ConfirmText "Yes" -CancelText "No" if (!$result) { if ($capturedState.DebugEnabled) { [Console]::WriteLine("[DEBUG] User cancelled") } return } } # Create async executor for action $actionExecutor = [PsUi.AsyncExecutor]::new() $actionExecutor.UiDispatcher = $capturedWindow.Dispatcher # Store executor in session for Stop-UiAsync cancellation $actionSession = [PsUi.SessionManager]::Current if ($actionSession) { $actionSession.ActiveExecutor = $actionExecutor } # Input provider for Read-Host $actionExecutor.InputProvider = { param($PromptText) try { $msg = if (![string]::IsNullOrWhiteSpace($PromptText)) { $PromptText } else { "The running action is requesting input." } if ($capturedShowInput) { return & $capturedShowInput -Title "Action Input Required" -Prompt $msg } return Show-UiInputDialog -Title "Action Input Required" -Prompt $msg } catch { return "" } } # Secure input provider for Read-Host -AsSecureString $actionExecutor.SecureInputProvider = { param($PromptText) $secureInput = $null try { $msg = if (![string]::IsNullOrWhiteSpace($PromptText)) { $PromptText } else { "The running action is requesting a password." } if ($capturedShowInput) { $secureInput = & $capturedShowInput -Title "Secure Input Required" -Prompt $msg -Password } else { $secureInput = Show-UiInputDialog -Title "Secure Input Required" -Prompt $msg -Password } } catch { return [System.Security.SecureString]::new() } if ($secureInput) { if ($secureInput -is [System.Security.SecureString]) { return $secureInput } return ConvertTo-SecureString $secureInput -AsPlainText -Force } return [System.Security.SecureString]::new() } # Choice Provider for -Confirm prompts $actionExecutor.ChoiceProvider = { param($Caption, $Message, $Choices, $DefaultChoice) return Show-UiChoiceDialog -Caption $Caption -Message $Message -Choices $Choices -DefaultChoice $DefaultChoice } # Credential Provider for Get-Credential $actionExecutor.CredentialProvider = { param($Caption, $Message, $UserName, $TargetName) return Show-UiCredentialDialog -Caption $Caption -Message $Message -UserName $UserName -TargetName $TargetName } # Prompt Provider for multi-field prompts $actionExecutor.PromptProvider = { param($Caption, $Message, $Descriptions) return Show-UiPromptDialog -Caption $Caption -Message $Message -Descriptions $Descriptions } # ReadKey Provider - handles "Press any key" patterns $actionExecutor.ReadKeyProvider = { param($Options) Show-UiMessageDialog -Title "Continue" -Message "Press OK to continue..." -Buttons OK -Icon Info } # ClearHost Provider - clears just the Console tab $actionExecutor.ClearHostProvider = { $capturedConsoleParagraph.Inlines.Clear() } # Store selected items, grid, and action name in $capturedState for OnComplete access $capturedState.ActiveGrid = $activeGrid $capturedState.SelectedItems = @($selected | ForEach-Object { $_ }) $capturedState.ActionName = $def.Text # Find and store the Action Status column reference $capturedState.StatusColumn = $null for ($i = 0; $i -lt $activeGrid.Columns.Count; $i++) { if ($activeGrid.Columns[$i].Header -eq 'Action Status') { $capturedState.StatusColumn = $activeGrid.Columns[$i] break } } # Set "Running" status directly on each cell if ($capturedState.StatusColumn) { foreach ($item in $capturedState.SelectedItems) { $row = $activeGrid.ItemContainerGenerator.ContainerFromItem($item) if ($row) { $cell = $capturedState.StatusColumn.GetCellContent($row) if ($cell -is [System.Windows.Controls.TextBlock]) { $cell.Text = "Running ($($def.Text))..." } } } } # Show spinner in header status indicator $capturedStatusSpinner.Visibility = 'Visible' $capturedStatusSuccess.Visibility = 'Collapsed' $capturedStatusWarning.Visibility = 'Collapsed' $capturedStatusIndicator.ClearValue([System.Windows.FrameworkElement]::ToolTipProperty) $capturedStatusIndicator.ToolTip = "Running ($($def.Text))..." # Track error state and item count in $capturedState $capturedState.ActionErrorOccurred = $false $capturedState.ActionErrorCount = 0 $capturedState.ItemCount = $capturedState.SelectedItems.Count $capturedState.CurrentItemIndex = 0 # Update UI - hide progress bar initially $capturedHeaderTitle.Text = "$capturedTitle - Running: $($def.Text)..." $capturedProgressPanel.Visibility = 'Collapsed' # Show Console tab and switch to it if ($capturedConsoleTab.Visibility -ne 'Visible') { $capturedConsoleTab.Visibility = 'Visible' } $capturedTabControl.SelectedItem = $capturedConsoleTab & $capturedAppendConsoleText "`n--- Action: $($def.Text) ---" ([System.Windows.Media.Brushes]::DarkCyan) -State $capturedAppendState # Wire up OnHost handler for console output $actionExecutor.add_OnHost({ param($record) if ($capturedState.IsCancelled) { return } $message = $null $fgColor = $null if ($record -is [PsUi.HostOutputRecord]) { $message = $record.Message $fgColor = $record.ForegroundColor } else { $message = "$record" } if ([string]::IsNullOrEmpty($message)) { return } if ($capturedConsoleTab.Visibility -ne 'Visible') { $capturedConsoleTab.Visibility = 'Visible' } if ($capturedTabControl.SelectedItem -ne $capturedConsoleTab) { $capturedTabControl.SelectedItem = $capturedConsoleTab } $brush = $null if ($null -ne $fgColor -and $capturedConsoleColorMap.ContainsKey($fgColor)) { $brush = $capturedConsoleColorMap[$fgColor] } & $capturedAppendConsoleText $message $brush -State $capturedAppendState }) # Wire up OnError handler $actionExecutor.add_OnError({ param($errorRecord) if ($capturedState.IsCancelled) { return } if ($null -eq $errorRecord) { return } if ($capturedErrorsTab.Visibility -eq 'Collapsed') { $capturedErrorsTab.Visibility = 'Visible' } $displayRecord = New-ErrorDisplayRecord -ErrorRecord $errorRecord [void]$capturedErrorsList.Add($displayRecord) $displayMessage = if ($errorRecord.Message) { $errorRecord.Message } else { $errorRecord.ToString() } & $capturedAppendConsoleText "[ERROR] $displayMessage" ([System.Windows.Media.Brushes]::IndianRed) -State $capturedAppendState $capturedState.ActionErrorOccurred = $true $capturedState.ActionErrorCount++ # Update badges if ($capturedTabControl.SelectedItem -ne $capturedErrorsTab) { $capturedTabNotifications.Errors.UnreadCount++ $totalErrors = $capturedErrorCount + $capturedState.ActionErrorCount $capturedErrorsTab.Header = "Errors ($totalErrors) +$($capturedTabNotifications.Errors.UnreadCount)" } if ($capturedTabControl.SelectedItem -ne $capturedConsoleTab) { $capturedTabNotifications.Console.UnreadCount++ $capturedConsoleTab.Header = "Console (+$($capturedTabNotifications.Console.UnreadCount))" } }) # Wire up OnProgress handler $actionExecutor.add_OnProgress({ param($progressRecord) if ($capturedState.IsCancelled) { return } if ($null -eq $progressRecord) { return } if ($progressRecord.RecordType -eq [System.Management.Automation.ProgressRecordType]::Completed) { $capturedProgressPanel.Visibility = 'Collapsed' return } $capturedProgressPanel.Visibility = 'Visible' if ($progressRecord.PercentComplete -ge 0) { $capturedProgressBar.IsIndeterminate = $false $capturedProgressBar.Value = $progressRecord.PercentComplete } else { $capturedProgressBar.IsIndeterminate = $true } $statusParts = [System.Collections.Generic.List[string]]::new() $statusParts.Add($capturedState.ActionName) if (![string]::IsNullOrWhiteSpace($progressRecord.Activity)) { $statusParts.Add($progressRecord.Activity) } if (![string]::IsNullOrWhiteSpace($progressRecord.StatusDescription)) { $statusParts.Add($progressRecord.StatusDescription) } if (![string]::IsNullOrWhiteSpace($progressRecord.CurrentOperation)) { $statusParts.Add("($($progressRecord.CurrentOperation))") } $capturedProgressLabel.Text = $statusParts -join " - " }) # Wire up OnComplete handler $actionExecutor.add_OnComplete({ if ($capturedState.IsCancelled) { return } if ($capturedState.DebugEnabled) { [Console]::WriteLine("[DEBUG] ResultAction '$($capturedState.ActionName)' complete - Errors: $($capturedState.ActionErrorCount)") } $capturedProgressPanel.Visibility = 'Collapsed' $capturedHeaderTitle.Text = "$capturedTitle - Complete" & $capturedAppendConsoleText "--- Action Complete ---" ([System.Windows.Media.Brushes]::DarkCyan) -State $capturedAppendState $capturedStatusSpinner.Visibility = 'Collapsed' $capturedStatusIndicator.ClearValue([System.Windows.FrameworkElement]::ToolTipProperty) if ($capturedState.ActionErrorOccurred) { $capturedStatusWarning.Visibility = 'Visible' $capturedStatusSuccess.Visibility = 'Collapsed' $capturedStatusIndicator.ToolTip = "Complete ($($capturedState.ActionName)) with errors" } else { $capturedStatusSuccess.Visibility = 'Visible' $capturedStatusWarning.Visibility = 'Collapsed' $capturedStatusIndicator.ToolTip = "Complete ($($capturedState.ActionName))" } # Update status column cells if ($capturedState.StatusColumn) { foreach ($item in $capturedState.SelectedItems) { $statusText = if ($capturedState.ActionErrorOccurred) { "Complete with errors ($($capturedState.ActionName))" } else { "Complete ($($capturedState.ActionName))" } $row = $capturedState.ActiveGrid.ItemContainerGenerator.ContainerFromItem($item) if ($row) { $cell = $capturedState.StatusColumn.GetCellContent($row) if ($cell -is [System.Windows.Controls.TextBlock]) { $cell.Text = $statusText } } } } }) # Build and execute the action script $selectedObjects = @($selected) $actionScriptString = $def.Action.ToString() $actionScript = { param($SelectedItems, $ActionScriptString) try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $global:OutputEncoding = [System.Text.Encoding]::UTF8 } catch { Write-Verbose "Failed to set UTF-8 encoding, using default: $_" } $Selected = $SelectedItems if (![string]::IsNullOrWhiteSpace($ActionScriptString)) { $ActionScriptBlock = [scriptblock]::Create($ActionScriptString) if ($Selected.Count -eq 1) { $_ = $Selected[0] } else { $_ = $Selected } & $ActionScriptBlock $Selected } } $actionParams = @{ SelectedItems = $selectedObjects ActionScriptString = $actionScriptString } $actionExecutor.ExecuteAsync($actionScript, $actionParams, $capturedVarValues, $capturedFuncDefs, $capturedModules) } catch { Show-UiMessageDialog -Message "Error: $($_.Exception.Message)" -Title "Error" -Icon "Error" } }.GetNewClosure()) } } |