internal/functions/Invoke-CallbackMenu.ps1
function Invoke-CallbackMenu { <# .SYNOPSIS Calls a GUI window to pick the contexts for a specific server. .DESCRIPTION Calls a GUI window to pick the contexts for a specific server. This is used when invoking Set-AdmfContext with the (hidden) -Callback parameter. It is designed to be triggered automatically when trying to manage a forest / domain that has not yet had its context defined. Note: This makes it critical to define a context first when doing unattended automation. .PARAMETER Server The server / domain being connected to. Used for documentation purposes, as well as to potentially determine initial checkbox state. .PARAMETER Credential The credentials to use for this operation. .EXAMPLE PS C:\> Invoke-CallbackMenu -Server contoso.com Shows the context selection menu for the domain contoso.com #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Server, [pscredential] $Credential ) begin { #region Utility Functions function New-CheckBox { [CmdletBinding()] param ( $ContextObject, $Parent ) $column = $Parent.Controls.Count % 2 $row = [math]::Truncate(($Parent.Controls.Count / 2)) $checkbox = [System.Windows.Forms.CheckBox]::new() $checkbox.Width = 200 $checkbox.Height = 20 $checkbox.AutoSize = $false $checkbox.Location = [System.Drawing.Point]::new((210 * $column + 15), (25 * $row + 15)) $checkbox.Text = $ContextObject.Name $checkbox.Font = 'Microsoft Sans Serif,10' $null = $Parent.Controls.Add($checkbox) $tooltip = [System.Windows.Forms.ToolTip]::new() $tooltip.ToolTipTitle = $ContextObject.Name $tooltipText = $ContextObject.Description if ($ContextObject.Prerequisites.Count -gt 0) { $tooltipText += "`nPrerequisites: $($ContextObject.Prerequisites -join ', ')" } if ($ContextObject.MutuallyExclusive.Count -gt 0) { $tooltipText += "`nMutually exclusive with: $($ContextObject.MutuallyExclusive -join ', ')" } $tooltip.SetToolTip($checkbox, $tooltipText) $checkbox.Add_CheckedChanged({ Update-Checkbox }) $checkbox } function Update-Checkbox { [CmdletBinding()] param () # Exemption: Accessing superscope variables directly. Forms and their events are screwey enough. foreach ($checkbox in $contextCheckboxes.Values) { $checkbox.Enabled = $true } foreach ($contextObject in $allContexts) { foreach ($prerequisite in $contextObject.Prerequisites) { if (-not $contextCheckboxes[$prerequisite].Checked) { $contextCheckboxes[$contextObject.Name].Enabled = $false $contextCheckboxes[$contextObject.Name].Checked = $false break } } foreach ($exclusion in $contextObject.MutuallyExclusive) { if (-not $contextCheckboxes[$contextObject.Name].Checked) { break } if (-not $contextCheckboxes[$exclusion]) { continue } $contextCheckboxes[$exclusion].Enabled = $false $contextCheckboxes[$exclusion].Checked = $false } } } function New-Form { [OutputType([System.Windows.Forms.Form])] [CmdletBinding()] param () New-Object System.Windows.Forms.Form -Property @{ ClientSize = '500,500' Text = "Context Selection" TopMost = $false AutoSize = $false } } function New-GroupBox { [OutputType([System.Windows.Forms.Groupbox])] [CmdletBinding()] param ( [string] $Text, [int] $Height, $Form ) $newHeight = 10 if ($Form.Controls.Count -gt 0) { $last = $Form.Controls | Sort-Object { $_.Location.Y } -Descending | Select-Object -First 1 $newHeight = 10 + $last.Height + $last.Location.Y } $groupBox = New-Object System.Windows.Forms.Groupbox -Property @{ Height = $Height Width = 480 Text = $Text AutoSize = $false Location = (New-Object System.Drawing.Point(10, $newHeight)) } $Form.Controls.Add($groupBox) $groupBox } function New-Label { [CmdletBinding()] param ( [string] $Text, $Parent ) $label = New-Object system.Windows.Forms.Label -Property @{ Text = $Text AutoSize = $false Font = 'Microsoft Sans Serif,10' Location = (New-Object System.Drawing.Point(10, 15)) Width = 460 TextAlign = 'MiddleCenter' } $Parent.Controls.Add($label) } #endregion Utility Functions $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential #region Form [System.Windows.Forms.Application]::EnableVisualStyles() $form = New-Form $group_Server = New-GroupBox -Text "Selected Domain / Server" -Height 50 -Form $form try { $domain = Get-ADDomain @parameters -ErrorAction Stop New-Label -Text $domain.DNSRoot -Parent $group_Server } catch { New-Label -Text $Server -Parent $group_Server } #region Contexts $allContexts = Get-AdmfContext $groupedContexts = $allContexts | Group-Object Group $contextCheckboxes = @{ } foreach ($groupedContext in $groupedContexts) { $rows = [math]::Round(($groupedContext.Group.Count / 2), [System.MidpointRounding]::AwayFromZero) $group_Context = New-GroupBox -Text $groupedContext.Name -Height ($rows * 25 + 15) -Form $form foreach ($contextObject in ($groupedContext.Group | Sort-Object Name)) { $contextCheckboxes[$contextObject.Name] = New-CheckBox -ContextObject $contextObject -Parent $group_Context } } if ($parameters.Server -eq '<Default Domain>') { $parameters.Server = $env:USERDNSDOMAIN } foreach ($context in $allContexts | Sort-Object Weight) { $path = Join-Path $context.Path 'contextPromptChecked.ps1' if (Test-Path $path) { try { $result = & $path @parameters if ($result) { $contextCheckboxes[$context.Name].Checked = $true } } catch { Write-PSFMessage -Level Warning -String 'Invoke-CallbackMenu.Context.Checked.Error' -StringValues $context.Name -ErrorRecord $_ } } } Update-Checkbox #endregion Contexts #region Buttons $button_Cancel = New-Object system.Windows.Forms.Button -Property @{ Text = 'Cancel' Width = 60 Height = 30 Anchor = 'right,bottom' Location = (New-Object System.Drawing.Point(426, 460)) Font = 'Microsoft Sans Serif,10' } $form.Controls.Add($button_Cancel) $button_OK = New-Object system.Windows.Forms.Button -Property @{ Text = 'OK' Width = 38 Height = 30 Anchor = 'right,bottom' Location = (New-Object System.Drawing.Point(378, 460)) Font = 'Microsoft Sans Serif,10' } $form.Controls.Add($button_OK) #endregion Buttons #region Other Stuff $okbox = [System.Windows.Forms.CheckBox]::new() $okbox.Visible = $false $form.Controls.Add($okbox) $button_OK.Add_Click({ $okbox.Checked = $true $this.Parent.Close() }) $form.ShowIcon = $false $form.CancelButton = $button_Cancel $form.AcceptButton = $button_OK $last = $form.Controls | Where-Object { $_ -is [System.Windows.Forms.Groupbox] } | Sort-Object { $_.Location.Y } -Descending | Select-Object -First 1 $newHeight = 90 + $last.Height + $last.Location.Y $form.Height = $newHeight #endregion Other Stuff #endregion Form } process { $null = $form.ShowDialog() if (-not $okbox.Checked) { throw "Interrupting: User cancelled operation" } $selectedNames = @(($contextCheckboxes.Values | Where-Object Checked).Text) $allContexts | Where-Object Name -In $selectedNames } } |