private/Select-ObjectFromGrid.ps1
|
function Select-ObjectFromGrid { <# .SYNOPSIS Displays a WPF-based data grid for selecting objects from a collection. .DESCRIPTION The Select-ObjectFromGrid function presents a XAML-based WPF window containing a DataGrid that displays objects from an input collection. Users can select one or more items using checkboxes and confirm their selection with OK or cancel the operation with Cancel. This function is similar to Out-GridView but provides additional features such as Select All/Deselect All buttons and minimum/maximum selection validation. With the ViewOnly switch the checkbox column and the selection controls are hidden, which turns the window into a read-only viewer. This is a usable alternative to Out-GridView on hosts where Out-GridView is unavailable, such as PowerShell 7 without the Microsoft.PowerShell.ConsoleGuiTools module, or Windows installations without the ISE components. .PARAMETER InputObject The collection of objects to display in the grid. Accepts pipeline input. .PARAMETER Title The title to display in the window title bar. Defaults to "Select Items". .PARAMETER Description The subtitle shown under the header inside the window. Defaults to a text matching the current mode. .PARAMETER ColumnsToShow An optional array of property names to display as columns. If not specified, all properties from the first object are displayed. .PARAMETER DetailsProperty The name of a property whose (usually long or multi-line) text is shown in an expandable details panel underneath the row instead of, or in addition to, a column. The panel appears when a row is selected. Excluded from the visible columns unless the same name is also explicitly listed in -ColumnsToShow. Its text is always searched by the global filter box. .PARAMETER MinSelection The minimum number of items that must be selected before OK can be clicked. Defaults to 0 (no minimum). Ignored when ViewOnly is used. .PARAMETER MaxSelection The maximum number of items that can be selected before OK can be clicked. Defaults to 0 (no maximum / unlimited). Ignored when ViewOnly is used. Note that this validates rather than blocks: the user can tick more items than MaxSelection, but OK stays disabled until the count is back within the limit. .PARAMETER ViewOnly Hides the checkbox column, the Select All / Deselect All buttons and the Cancel button, and turns the window into a read-only viewer with a single Close button. No objects are returned in this mode. NoSelection is an alias for this parameter. .EXAMPLE $selected = Get-Process | Select-Object -Property Name, Id, CPU | Select-ObjectFromGrid Displays all processes in a grid and returns the selected items. .EXAMPLE $users = Get-ADUser -Filter * -Properties DisplayName, EmailAddress $selected = $users | Select-ObjectFromGrid -Title "Select Users" -ColumnsToShow DisplayName, EmailAddress Displays AD users with only DisplayName and EmailAddress columns. .EXAMPLE $items | Select-ObjectFromGrid -MinSelection 1 -MaxSelection 5 Requires at least 1 selection and allows maximum 5 selections. .EXAMPLE $entries | Select-ObjectFromGrid -Title "Log" -ColumnsToShow Timestamp, Message -DetailsProperty Details -ViewOnly Shows Timestamp and Message as columns; selecting a row expands a panel showing the entry's Details text underneath it. .EXAMPLE Get-Service | Select-Object -Property Name, Status, StartType | Select-ObjectFromGrid -Title "Services" -ViewOnly Displays the services in a read-only viewer without checkboxes, as an alternative to Out-GridView. Nothing is returned. .OUTPUTS PSCustomObject[] Returns an array of the selected input objects, or $null if cancelled. Returns nothing when ViewOnly is used. .NOTES Function : Select-ObjectFromGrid Author : John Billekens Copyright : Copyright (c) John Billekens Consultancy Version : 2026.720.1245 Requires : Windows PowerShell 5.1 or PowerShell 7+ with Windows Desktop support #> [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [PSObject[]]$InputObject, [Parameter(Mandatory = $false)] [string]$Title = "Select Items", [Parameter(Mandatory = $false)] [string]$Description, [Parameter(Mandatory = $false)] [string[]]$ColumnsToShow, [Parameter(Mandatory = $false)] [string]$DetailsProperty, [Parameter(Mandatory = $false)] [ValidateRange(0, [int]::MaxValue)] [int]$MinSelection = 0, [Parameter(Mandatory = $false)] [ValidateRange(0, [int]::MaxValue)] [int]$MaxSelection = 0, [Parameter(Mandatory = $false)] [Alias('NoSelection')] [switch]$ViewOnly ) begin { $allItems = [System.Collections.Generic.List[object]]::new() } process { foreach ($item in $InputObject) { $allItems.Add($item) } } end { if ($allItems.Count -eq 0) { Write-Warning "No items to display." return $null } if ($MinSelection -gt 0 -and $MaxSelection -gt 0 -and $MinSelection -gt $MaxSelection) { throw "MinSelection ($MinSelection) cannot be greater than MaxSelection ($MaxSelection)." } # ShowDialog() requires STA; MTA throws unhandled on the dispatcher and kills the process. if ([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne [System.Threading.ApartmentState]::STA) { throw "Select-ObjectFromGrid requires a single-threaded apartment (STA) thread to show its window. " + ` "Restart PowerShell with 'powershell.exe -STA' or 'pwsh -STA', or run this from a host that already uses STA (e.g. the default Windows PowerShell 5.1 console)." } # Load WPF assemblies Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName PresentationCore Add-Type -AssemblyName WindowsBase # Determine columns to display $firstItem = $allItems[0] if ($ColumnsToShow -and $ColumnsToShow.Count -gt 0) { $columns = @($ColumnsToShow) } else { $columns = @($firstItem.PSObject.Properties.Name) } # Details property gets its own panel unless the caller explicitly kept it in ColumnsToShow. if ($DetailsProperty -and -not ($ColumnsToShow -and $ColumnsToShow -contains $DetailsProperty)) { $columns = @($columns | Where-Object { $_ -ne $DetailsProperty }) } if ($columns.Count -eq 0) { Write-Warning "No columns to display." return $null } if (-not $PSBoundParameters.ContainsKey('Description')) { $Description = if ($ViewOnly) { "Review the items below and close the window when you are done." } else { "Manage your selection and confirm your choice." } } # Column names are built in code, not via XAML string interpolation. $escapeXml = { param([string]$Text) [System.Security.SecurityElement]::Escape($Text) } # The checkbox column and the selection controls only exist in selection mode. if ($ViewOnly) { $checkBoxColumnXaml = "" $selectionPanelVisibility = "Collapsed" $cancelButtonVisibility = "Collapsed" $okButtonText = "Close" $headerText = & $escapeXml $Title } else { $checkBoxColumnXaml = @" <DataGridTemplateColumn Header="" Width="50" CanUserResize="False"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <CheckBox Name="chkSelect" IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="4,0"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> "@ $selectionPanelVisibility = "Visible" $cancelButtonVisibility = "Visible" $okButtonText = "OK" $headerText = & $escapeXml $Title } $titleXml = & $escapeXml $Title $descriptionXml = & $escapeXml $Description # Create the XAML for the window $xaml = @" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="$titleXml" Width="1440" Height="900" WindowStartupLocation="CenterScreen" ResizeMode="CanResize" MinWidth="600" MinHeight="400"> <Window.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="#0F172A" Offset="0"/> <GradientStop Color="#1E293B" Offset="0.6"/> <GradientStop Color="#111827" Offset="1"/> </LinearGradientBrush> </Window.Background> <Window.Resources> <!-- Palette --> <Color x:Key="AccentColor">#3B82F6</Color> <Color x:Key="CardColor">#0B1221</Color> <SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/> <SolidColorBrush x:Key="AccentBrushLight" Color="#1F6FEB"/> <SolidColorBrush x:Key="CardBrush" Color="{StaticResource CardColor}"/> <SolidColorBrush x:Key="CardBorderBrush" Color="#24324D"/> <SolidColorBrush x:Key="TextBrush" Color="#F8FAFC"/> <SolidColorBrush x:Key="TextSubtleBrush" Color="#CBD5E1"/> <SolidColorBrush x:Key="BorderBrushLight" Color="#1F2937"/> <!-- Buttons --> <Style TargetType="Button"> <Setter Property="Foreground" Value="#FFFFFF"/> <Setter Property="Background" Value="{StaticResource AccentBrush}"/> <Setter Property="Padding" Value="18,11"/> <Setter Property="MinWidth" Value="120"/> <Setter Property="MinHeight" Value="38"/> <Setter Property="Margin" Value="6,0,0,0"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="FontWeight" Value="SemiBold"/> <Setter Property="FontSize" Value="13"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border x:Name="ButtonBg" Background="{TemplateBinding Background}" BorderBrush="Transparent" BorderThickness="1" CornerRadius="8"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#2F6FE3"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#2257B8"/> </Trigger> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter TargetName="ButtonBg" Property="BorderBrush" Value="#93C5FD"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Background" Value="#232E45"/> <Setter Property="Foreground" Value="#5B6B85"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- Secondary (ghost) button --> <Style x:Key="GhostButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}"> <Setter Property="Background" Value="#182033"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> <Setter Property="BorderBrush" Value="#2D3B54"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border x:Name="GhostBg" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="8"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#22304C"/> <Setter TargetName="GhostBg" Property="BorderBrush" Value="#3A4C6E"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#151D31"/> </Trigger> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter TargetName="GhostBg" Property="BorderBrush" Value="{StaticResource AccentBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- DataGrid --> <Style TargetType="DataGrid"> <Setter Property="BorderThickness" Value="0"/> <Setter Property="RowBackground" Value="#0F172A"/> <Setter Property="AlternatingRowBackground" Value="#111B2F"/> <Setter Property="Background" Value="#0B1221"/> <Setter Property="GridLinesVisibility" Value="None"/> <Setter Property="RowHeaderWidth" Value="0"/> <Setter Property="HeadersVisibility" Value="Column"/> <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrushLight}"/> <Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrushLight}"/> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="Margin" Value="0"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> </Style> <Style TargetType="DataGridColumnHeader"> <Setter Property="Background" Value="#142038"/> <Setter Property="Foreground" Value="{StaticResource TextSubtleBrush}"/> <Setter Property="FontWeight" Value="SemiBold"/> <Setter Property="BorderBrush" Value="{StaticResource BorderBrushLight}"/> <Setter Property="BorderThickness" Value="0,0,0,1"/> <Setter Property="Padding" Value="12,10"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="Cursor" Value="Hand"/> </Style> <!-- Per-column filter TextBox, rounded to match the global filter box --> <Style x:Key="ColumnFilterBox" TargetType="TextBox"> <Setter Property="Background" Value="#0F172A"/> <Setter Property="BorderBrush" Value="#2D3B54"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> <Setter Property="CaretBrush" Value="{StaticResource TextBrush}"/> <Setter Property="FontSize" Value="11"/> <Setter Property="FontWeight" Value="Normal"/> <Setter Property="Padding" Value="8,4"/> <Setter Property="Margin" Value="0,6,0,0"/> <Setter Property="MinWidth" Value="80"/> <Setter Property="Cursor" Value="IBeam"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TextBox"> <Border x:Name="FilterBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="6"> <Grid> <TextBlock x:Name="FilterHint" Text="Filter" Foreground="#4B5A75" Margin="{TemplateBinding Padding}" VerticalAlignment="Center" IsHitTestVisible="False" Visibility="Collapsed"/> <ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter TargetName="FilterBorder" Property="BorderBrush" Value="{StaticResource AccentBrush}"/> </Trigger> <Trigger Property="Text" Value=""> <Setter TargetName="FilterHint" Property="Visibility" Value="Visible"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- Thin dark scrollbars --> <Style x:Key="GridThumb" TargetType="Thumb"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Thumb"> <Border x:Name="ThumbBorder" Background="#3B4E75" CornerRadius="5"/> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="ThumbBorder" Property="Background" Value="#4A5F8C"/> </Trigger> <Trigger Property="IsDragging" Value="True"> <Setter TargetName="ThumbBorder" Property="Background" Value="#5A6F9E"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="GridRepeatButton" TargetType="RepeatButton"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="RepeatButton"> <Rectangle Fill="Transparent"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="ScrollBar"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Width" Value="12"/> <Setter Property="MinWidth" Value="12"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ScrollBar"> <Grid Background="Transparent"> <Border Background="#131D33" CornerRadius="5" Margin="2,0"/> <Track Name="PART_Track" IsDirectionReversed="True"> <Track.DecreaseRepeatButton> <RepeatButton Style="{StaticResource GridRepeatButton}" Command="ScrollBar.PageUpCommand"/> </Track.DecreaseRepeatButton> <Track.IncreaseRepeatButton> <RepeatButton Style="{StaticResource GridRepeatButton}" Command="ScrollBar.PageDownCommand"/> </Track.IncreaseRepeatButton> <Track.Thumb> <Thumb Style="{StaticResource GridThumb}" Width="8" Margin="2,0"/> </Track.Thumb> </Track> </Grid> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="Orientation" Value="Horizontal"> <Setter Property="Height" Value="12"/> <Setter Property="MinHeight" Value="12"/> <Setter Property="Width" Value="Auto"/> <Setter Property="MinWidth" Value="0"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ScrollBar"> <Grid Background="Transparent"> <Border Background="#131D33" CornerRadius="5" Margin="0,2"/> <Track Name="PART_Track" IsDirectionReversed="False"> <Track.DecreaseRepeatButton> <RepeatButton Style="{StaticResource GridRepeatButton}" Command="ScrollBar.PageLeftCommand"/> </Track.DecreaseRepeatButton> <Track.IncreaseRepeatButton> <RepeatButton Style="{StaticResource GridRepeatButton}" Command="ScrollBar.PageRightCommand"/> </Track.IncreaseRepeatButton> <Track.Thumb> <Thumb Style="{StaticResource GridThumb}" Height="8" Margin="0,2"/> </Track.Thumb> </Track> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> <Style TargetType="DataGridRow"> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="Margin" Value="0,0,0,2"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#16233C"/> </Trigger> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="#1E3A8A"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsSelected" Value="True"/> <Condition Property="IsKeyboardFocusWithin" Value="False"/> </MultiTrigger.Conditions> <Setter Property="Background" Value="#243451"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> </MultiTrigger> <!-- Right-click gives the row keyboard focus without selecting it; default WPF chrome for a focused-but-unselected row is a light system color. --> <Trigger Property="IsFocused" Value="True"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> </Trigger> </Style.Triggers> </Style> <Style TargetType="DataGridCell"> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Padding" Value="14,12"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Style.Triggers> <Trigger Property="IsFocused" Value="True"> <Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderBrush" Value="{StaticResource AccentBrush}"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> </Trigger> </Style.Triggers> </Style> <Style TargetType="CheckBox"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> <Setter Property="BorderBrush" Value="#9CA3AF"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="CheckBox"> <Grid VerticalAlignment="Center" HorizontalAlignment="Center"> <Border x:Name="Border" Width="16" Height="16" CornerRadius="3" BorderThickness="1" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}"/> <Path x:Name="CheckMark" Data="M2,6 L6,10 L14,2" StrokeThickness="2" Stroke="#F8FAFC" StrokeEndLineCap="Round" StrokeStartLineCap="Round" StrokeLineJoin="Round" Visibility="Collapsed"/> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="Border" Property="Background" Value="#3B82F6"/> <Setter TargetName="Border" Property="BorderBrush" Value="#3B82F6"/> <Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/> </Trigger> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Border" Property="BorderBrush" Value="#5B8EF6"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter TargetName="Border" Property="Opacity" Value="0.5"/> <Setter TargetName="CheckMark" Property="Opacity" Value="0.5"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="ContextMenuItemDarkStyle" TargetType="MenuItem"> <Setter Property="Foreground" Value="{StaticResource TextBrush}"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Padding" Value="10,6"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="MenuItem"> <Border x:Name="ItemBorder" Background="{TemplateBinding Background}" CornerRadius="5"> <ContentPresenter ContentSource="Header" Margin="{TemplateBinding Padding}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsHighlighted" Value="True"> <Setter TargetName="ItemBorder" Property="Background" Value="{StaticResource AccentBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="ContextMenuDarkStyle" TargetType="ContextMenu"> <Setter Property="Background" Value="{StaticResource CardBrush}"/> <Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="4"/> <Setter Property="HasDropShadow" Value="False"/> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="ItemContainerStyle" Value="{StaticResource ContextMenuItemDarkStyle}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ContextMenu"> <Border Background="{StaticResource CardBrush}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="8" SnapsToDevicePixels="True"> <Border.Effect> <DropShadowEffect Color="#33000000" BlurRadius="14" ShadowDepth="0" Opacity="0.35"/> </Border.Effect> <StackPanel IsItemsHost="True" Margin="{TemplateBinding Padding}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid Margin="24"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <!-- Header --> <StackPanel Grid.Row="0" Margin="0,0,0,16"> <TextBlock Text="$headerText" FontSize="24" FontWeight="Bold" Foreground="{StaticResource TextBrush}"/> <TextBlock Text="$descriptionXml" FontSize="13" Foreground="{StaticResource TextSubtleBrush}" Margin="0,4,0,0"/> </StackPanel> <!-- Card --> <Border Grid.Row="1" Background="{StaticResource CardBrush}" CornerRadius="12" Padding="20" BorderBrush="{StaticResource CardBorderBrush}" BorderThickness="1"> <Border.Effect> <DropShadowEffect Color="#33000000" BlurRadius="18" ShadowDepth="0" Opacity="0.28"/> </Border.Effect> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <!-- Filter and selection actions --> <Grid Grid.Row="0" Margin="0,0,0,12"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left"> <Border Name="borderFilter" Background="#0F172A" BorderBrush="#2D3B54" BorderThickness="1" CornerRadius="8" Padding="10,6" MinWidth="280"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid Grid.Column="0" Width="15" Height="15" Margin="2,0,8,0" VerticalAlignment="Center"> <Ellipse Width="10" Height="10" Stroke="#64748B" StrokeThickness="1.6" HorizontalAlignment="Left" VerticalAlignment="Top"/> <Line X1="9.5" Y1="9.5" X2="13.5" Y2="13.5" Stroke="#64748B" StrokeThickness="1.6" StrokeEndLineCap="Round"/> </Grid> <TextBlock Grid.Column="1" Name="txtFilterHint" Text="Search all columns..." Foreground="#64748B" VerticalAlignment="Center" IsHitTestVisible="False" Margin="2,0,0,0"/> <TextBox Grid.Column="1" Name="txtFilter" Background="Transparent" BorderThickness="0" Foreground="{StaticResource TextBrush}" CaretBrush="{StaticResource TextBrush}" VerticalAlignment="Center"/> </Grid> </Border> <Button Name="btnClearFilters" Content="Clear filters" Style="{StaticResource GhostButton}"/> </StackPanel> <StackPanel Grid.Column="1" Orientation="Horizontal"> <StackPanel Name="pnlSelectionActions" Orientation="Horizontal" Visibility="$selectionPanelVisibility"> <Button Name="btnSelectAll" Content="Select All" Style="{StaticResource GhostButton}" Margin="0,0,10,0"/> <Button Name="btnDeselectAll" Content="Deselect All" Style="{StaticResource GhostButton}"/> </StackPanel> <TextBlock Name="txtSelectionInfo" VerticalAlignment="Center" Margin="16,0,0,0" FontSize="12" Foreground="{StaticResource TextSubtleBrush}"/> </StackPanel> </Grid> <!-- Data Grid --> <DataGrid Grid.Row="1" Name="dataGrid" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="False" SelectionMode="Extended" SelectionUnit="FullRow" IsReadOnly="True" AlternationCount="2" EnableRowVirtualization="True" EnableColumnVirtualization="False" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.ScrollUnit="Pixel" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"> <DataGrid.RowDetailsTemplate> <DataTemplate> <Border x:Name="DetailsBorder" Background="#0D1526" BorderBrush="#24324D" BorderThickness="0,1,0,1" Padding="18,10"> <TextBox Text="{Binding _DetailsText, Mode=OneWay}" IsReadOnly="True" BorderThickness="0" Background="Transparent" Foreground="#CBD5E1" FontFamily="Consolas" FontSize="12" TextWrapping="Wrap"/> </Border> <DataTemplate.Triggers> <DataTrigger Binding="{Binding _DetailsText}" Value=""> <Setter TargetName="DetailsBorder" Property="Visibility" Value="Collapsed"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </DataGrid.RowDetailsTemplate> <DataGrid.Columns> $checkBoxColumnXaml </DataGrid.Columns> </DataGrid> <!-- Status --> <TextBlock Grid.Row="2" Name="txtStatus" Margin="0,12,0,0" FontSize="12" Foreground="{StaticResource TextSubtleBrush}"/> </Grid> </Border> <!-- Footer buttons --> <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,18,0,0"> <Button Name="btnCancel" Content="Cancel" Style="{StaticResource GhostButton}" IsCancel="True" Margin="0,0,10,0" Visibility="$cancelButtonVisibility"/> <Button Name="btnOK" Content="$okButtonText" IsDefault="True"/> </StackPanel> </Grid> </Window> "@ # Ordered hashtable; Add-Member per property is far slower. $wrappedItems = [System.Collections.Generic.List[object]]::new($allItems.Count) foreach ($item in $allItems) { $properties = [ordered]@{} if (-not $ViewOnly) { $properties['IsSelected'] = $false } foreach ($col in $columns) { $value = $item.$col if ($null -ne $value) { # Unwrap PSObject; WPF's sort comparer can't compare the wrapper. $value = $value.PSObject.BaseObject } $properties[$col] = $value } if ($DetailsProperty) { $detailsValue = $item.$DetailsProperty $properties['_DetailsText'] = if ($null -eq $detailsValue) { '' } else { [string]$detailsValue } } $properties['_OriginalItem'] = $item $wrappedItems.Add([PSCustomObject]$properties) } # Parse XAML try { $reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml)) try { $window = [System.Windows.Markup.XamlReader]::Load($reader) } finally { $reader.Close() } } catch [System.Windows.Markup.XamlParseException] { throw "Failed to build the grid window: $($_.Exception.Message)" } # Get controls $dataGrid = $window.FindName("dataGrid") $dataGrid.RowDetailsVisibilityMode = if ($DetailsProperty) { [System.Windows.Controls.DataGridRowDetailsVisibilityMode]::VisibleWhenSelected } else { [System.Windows.Controls.DataGridRowDetailsVisibilityMode]::Collapsed } $btnSelectAll = $window.FindName("btnSelectAll") $btnDeselectAll = $window.FindName("btnDeselectAll") $btnOK = $window.FindName("btnOK") $btnCancel = $window.FindName("btnCancel") $txtSelectionInfo = $window.FindName("txtSelectionInfo") $txtStatus = $window.FindName("txtStatus") $txtFilter = $window.FindName("txtFilter") $txtFilterHint = $window.FindName("txtFilterHint") $btnClearFilters = $window.FindName("btnClearFilters") $borderFilter = $window.FindName("borderFilter") $columnFilterBoxStyle = $window.FindResource("ColumnFilterBox") $accentBrush = $window.FindResource("AccentBrush") $defaultFilterBorderBrush = $borderFilter.BorderBrush # Focus highlight, matches per-column filter box style. $txtFilter.Add_GotFocus({ $borderFilter.BorderBrush = $accentBrush }.GetNewClosure()) $txtFilter.Add_LostFocus({ $borderFilter.BorderBrush = $defaultFilterBorderBrush }.GetNewClosure()) # Columns built in code; arbitrary property names could break XAML binding paths. $cellStyle = [System.Windows.Style]::new([System.Windows.Controls.TextBlock]) $cellStyle.Setters.Add([System.Windows.Setter]::new( [System.Windows.Controls.TextBlock]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)) $cellStyle.Setters.Add([System.Windows.Setter]::new( [System.Windows.FrameworkElement]::MarginProperty, [System.Windows.Thickness]::new(10, 6, 10, 6))) $cellStyle.Setters.Add([System.Windows.Setter]::new( [System.Windows.Controls.TextBlock]::ForegroundProperty, $window.FindResource("TextBrush"))) # A collection view gives filtering/sorting without touching the backing list. $view = [System.Windows.Data.CollectionViewSource]::GetDefaultView($wrappedItems) $state = @{ DialogResult = $null SelectedItems = @() WrappedItems = $wrappedItems Columns = $columns View = $view DataGrid = $dataGrid Window = $window MinSelection = $MinSelection MaxSelection = $MaxSelection ViewOnly = [bool]$ViewOnly TxtSelectionInfo = $txtSelectionInfo TxtStatus = $txtStatus TxtFilterHint = $txtFilterHint BtnOK = $btnOK # Tracked incrementally, no full rescan per click. SelectedCount = 0 ColumnFilters = @{} ColumnFilterBoxes = @{} SortArrows = @{} PopupCloseTicks = @{} DetailsKey = if ($DetailsProperty) { '_DetailsText' } else { $null } SortColumn = $null SortDirection = 0 } $state.UpdateSelection = { $selectedCount = $state.SelectedCount $totalCount = $state.WrappedItems.Count $infoText = "$selectedCount of $totalCount selected" if ($state.MinSelection -gt 0 -or $state.MaxSelection -gt 0) { $constraints = @() if ($state.MinSelection -gt 0) { $constraints += "min: $($state.MinSelection)" } if ($state.MaxSelection -gt 0) { $constraints += "max: $($state.MaxSelection)" } $infoText += " (" + ($constraints -join ", ") + ")" } $visibleCount = $state.View.Count if ($visibleCount -lt $totalCount) { $infoText += " - showing $visibleCount of $totalCount" } $state.TxtSelectionInfo.Text = $infoText $valid = $true $statusText = "" if ($state.MinSelection -gt 0 -and $selectedCount -lt $state.MinSelection) { $valid = $false $statusText = "Please select at least $($state.MinSelection) item(s)." } elseif ($state.MaxSelection -gt 0 -and $selectedCount -gt $state.MaxSelection) { $valid = $false $statusText = "Please select no more than $($state.MaxSelection) item(s)." } $state.TxtStatus.Text = $statusText $state.TxtStatus.Foreground = if ($valid) { [System.Windows.Media.Brushes]::Gray } else { [System.Windows.Media.Brushes]::Red } $state.BtnOK.IsEnabled = $valid }.GetNewClosure() # ViewOnly status text (no selection counter). $state.UpdateFilterStatus = { $totalCount = $state.WrappedItems.Count $visibleCount = $state.View.Count if ($visibleCount -lt $totalCount) { $state.TxtSelectionInfo.Text = "showing $visibleCount of $totalCount item(s)" } else { $state.TxtSelectionInfo.Text = "$totalCount item(s)" } }.GetNewClosure() # Reapplies filter and refreshes status text. $state.RefreshFilterState = { $anyActive = (-not [string]::IsNullOrEmpty($state.Filter)) if (-not $anyActive) { foreach ($needle in $state.ColumnFilters.Values) { if (-not [string]::IsNullOrEmpty($needle)) { $anyActive = $true; break } } } if ($anyActive) { $state.View.Filter = $state.FilterPredicate } else { $state.View.Filter = $null } $state.View.Refresh() if ($state.ViewOnly) { & $state.UpdateFilterStatus } else { & $state.UpdateSelection } }.GetNewClosure() # 3-state: unsorted -> ascending -> descending -> unsorted. Single column only. $state.ApplySort = { param([string]$ColumnName) if ($state.SortColumn -eq $ColumnName) { $state.SortDirection = $state.SortDirection + 1 if ($state.SortDirection -gt 2) { $state.SortDirection = 0 } } else { $state.SortColumn = $ColumnName $state.SortDirection = 1 } if ($state.SortDirection -eq 0) { $state.SortColumn = $null } $state.View.SortDescriptions.Clear() if ($state.SortColumn) { $direction = if ($state.SortDirection -eq 1) { [System.ComponentModel.ListSortDirection]::Ascending } else { [System.ComponentModel.ListSortDirection]::Descending } try { [void]$state.View.SortDescriptions.Add([System.ComponentModel.SortDescription]::new($state.SortColumn, $direction)) } catch { # Non-comparable values. $state.View.SortDescriptions.Clear() $state.SortColumn = $null $state.SortDirection = 0 $state.TxtStatus.Text = "Column '$ColumnName' contains values that cannot be sorted." } } foreach ($name in $state.SortArrows.Keys) { $arrow = $state.SortArrows[$name] if ($name -eq $state.SortColumn -and $state.SortDirection -eq 1) { $arrow.Text = [char]0x25B2 } elseif ($name -eq $state.SortColumn -and $state.SortDirection -eq 2) { $arrow.Text = [char]0x25BC } else { $arrow.Text = "" } } }.GetNewClosure() $funnelIdleBrush = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.Color]::FromRgb(0x64, 0x74, 0x8B)) $funnelIdleBrush.Freeze() $popupBorderBrush = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.Color]::FromRgb(0x2D, 0x3B, 0x54)) $popupBorderBrush.Freeze() # Header: title (sort) + funnel icon (filter popup). foreach ($col in $columns) { $colName = $col $binding = [System.Windows.Data.Binding]::new() $binding.Path = [System.Windows.PropertyPath]::new($colName) $binding.Mode = [System.Windows.Data.BindingMode]::OneWay $column = [System.Windows.Controls.DataGridTextColumn]::new() $column.Binding = $binding $column.IsReadOnly = $true $column.CanUserSort = $false # CanUserSort is off; SortMemberPath just carries the column name to the Click handler. $column.SortMemberPath = $colName $column.Width = [System.Windows.Controls.DataGridLength]::Auto $column.ElementStyle = $cellStyle # Title + sort arrow. $titleBlock = [System.Windows.Controls.TextBlock]::new() $titleBlock.Text = $colName $titleBlock.VerticalAlignment = [System.Windows.VerticalAlignment]::Center $arrowBlock = [System.Windows.Controls.TextBlock]::new() $arrowBlock.Text = "" $arrowBlock.Margin = [System.Windows.Thickness]::new(4, 0, 0, 0) $arrowBlock.VerticalAlignment = [System.Windows.VerticalAlignment]::Center $arrowBlock.FontSize = 10 $arrowBlock.Foreground = $window.FindResource("AccentBrush") $state.SortArrows[$colName] = $arrowBlock $titleRow = [System.Windows.Controls.StackPanel]::new() $titleRow.Orientation = [System.Windows.Controls.Orientation]::Horizontal [void]$titleRow.Children.Add($titleBlock) [void]$titleRow.Children.Add($arrowBlock) # Filter popup trigger. $funnel = [System.Windows.Shapes.Path]::new() $funnel.Data = [System.Windows.Media.Geometry]::Parse("M0,0 H12 L7,5.5 V10.5 L5,9 V5.5 Z") $funnel.Fill = $funnelIdleBrush $funnel.VerticalAlignment = [System.Windows.VerticalAlignment]::Center $funnelHost = [System.Windows.Controls.Border]::new() $funnelHost.Background = [System.Windows.Media.Brushes]::Transparent $funnelHost.Padding = [System.Windows.Thickness]::new(5, 3, 5, 3) $funnelHost.Margin = [System.Windows.Thickness]::new(2, 0, 0, 0) $funnelHost.VerticalAlignment = [System.Windows.VerticalAlignment]::Center $funnelHost.Cursor = [System.Windows.Input.Cursors]::Hand $funnelHost.ToolTip = "Filter this column" $funnelHost.Child = $funnel $filterBox = [System.Windows.Controls.TextBox]::new() $filterBox.Style = $columnFilterBoxStyle $filterBox.Width = 180 $filterBox.Margin = [System.Windows.Thickness]::new(0) $state.ColumnFilters[$colName] = $null $state.ColumnFilterBoxes[$colName] = $filterBox $state.PopupCloseTicks[$colName] = 0L $popupBorder = [System.Windows.Controls.Border]::new() $popupBorder.Background = $window.FindResource("CardBrush") $popupBorder.BorderBrush = $popupBorderBrush $popupBorder.BorderThickness = [System.Windows.Thickness]::new(1) $popupBorder.CornerRadius = [System.Windows.CornerRadius]::new(8) $popupBorder.Padding = [System.Windows.Thickness]::new(10) $popupBorder.Child = $filterBox $popup = [System.Windows.Controls.Primitives.Popup]::new() $popup.AllowsTransparency = $true $popup.StaysOpen = $false $popup.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Bottom $popup.PlacementTarget = $funnelHost $popup.VerticalOffset = 4 $popup.Child = $popupBorder $popup.Add_Closed({ $state.PopupCloseTicks[$colName] = [DateTime]::UtcNow.Ticks }.GetNewClosure()) # Header captures mouse on down; only Preview reaches here. Handled=true blocks sort. # Time guard stops StaysOpen=False from reopening on the same click. $funnelHost.Add_PreviewMouseLeftButtonDown({ $_.Handled = $true if ($popup.IsOpen) { $popup.IsOpen = $false } elseif (([DateTime]::UtcNow.Ticks - $state.PopupCloseTicks[$colName]) -gt 3000000) { $popup.IsOpen = $true [void]$state.Window.Dispatcher.BeginInvoke( [System.Windows.Threading.DispatcherPriority]::Input, [System.Action]{ [void]$filterBox.Focus() }.GetNewClosure()) } }.GetNewClosure()) $filterBox.Add_KeyDown({ if ($_.Key -eq [System.Windows.Input.Key]::Escape -or $_.Key -eq [System.Windows.Input.Key]::Enter) { $popup.IsOpen = $false } }.GetNewClosure()) $filterBox.Add_TextChanged({ $text = $filterBox.Text if ([string]::IsNullOrWhiteSpace($text)) { $state.ColumnFilters[$colName] = $null $funnel.Fill = $funnelIdleBrush } else { $state.ColumnFilters[$colName] = $text $funnel.Fill = $accentBrush } & $state.RefreshFilterState }.GetNewClosure()) $headerPanel = [System.Windows.Controls.StackPanel]::new() $headerPanel.Orientation = [System.Windows.Controls.Orientation]::Horizontal [void]$headerPanel.Children.Add($titleRow) [void]$headerPanel.Children.Add($funnelHost) [void]$headerPanel.Children.Add($popup) $column.Header = $headerPanel $dataGrid.Columns.Add($column) } $btnClearFilters.Add_Click({ $txtFilter.Text = "" foreach ($box in $state.ColumnFilterBoxes.Values) { $box.Text = "" } }.GetNewClosure()) # Header captures mouse; child MouseUp handlers never fire. Use header's own Click. $dataGrid.AddHandler( [System.Windows.Controls.Primitives.DataGridColumnHeader]::ClickEvent, [System.Windows.RoutedEventHandler] { $header = $_.OriginalSource if ($header -is [System.Windows.Controls.Primitives.DataGridColumnHeader] -and $null -ne $header.Column -and -not [string]::IsNullOrEmpty($header.Column.SortMemberPath)) { & $state.ApplySort $header.Column.SortMemberPath } }.GetNewClosure() ) # Ctrl+C copies the focused cell; context menu adds explicit Copy Cell / Copy Row. $copyCellMenuItem = [System.Windows.Controls.MenuItem]::new() $copyCellMenuItem.Header = "Copy Cell" $copyRowMenuItem = [System.Windows.Controls.MenuItem]::new() $copyRowMenuItem.Header = "Copy Row" $gridContextMenu = [System.Windows.Controls.ContextMenu]::new() # Code-created ContextMenu is reparented under a Popup adorner; implicit # Window.Resources styling by TargetType does not reliably apply there. $gridContextMenu.Style = $window.FindResource("ContextMenuDarkStyle") [void]$gridContextMenu.Items.Add($copyCellMenuItem) [void]$gridContextMenu.Items.Add($copyRowMenuItem) $dataGrid.ContextMenu = $gridContextMenu # Right-click must not change row selection (it would toggle the details panel). # Move CurrentCell to the cell under the pointer so Copy Cell/Row still target it. $dataGrid.AddHandler( [System.Windows.Controls.DataGridCell]::PreviewMouseRightButtonDownEvent, [System.Windows.Input.MouseButtonEventHandler] { $cellElement = $_.OriginalSource while ($cellElement -and -not ($cellElement -is [System.Windows.Controls.DataGridCell])) { $cellElement = [System.Windows.Media.VisualTreeHelper]::GetParent($cellElement) } if ($cellElement -and $cellElement.Column) { $state.DataGrid.CurrentCell = [System.Windows.Controls.DataGridCellInfo]::new($cellElement.DataContext, $cellElement.Column) } $_.Handled = $true }.GetNewClosure() ) $state.CopyCurrentCell = { $cell = $state.DataGrid.CurrentCell if ($null -eq $cell.Column -or $null -eq $cell.Item -or [string]::IsNullOrEmpty($cell.Column.SortMemberPath)) { return } $value = $cell.Item.($cell.Column.SortMemberPath) [System.Windows.Clipboard]::SetText($(if ($null -eq $value) { '' } else { $value.ToString() })) }.GetNewClosure() $state.CopyCurrentRow = { $item = $state.DataGrid.CurrentCell.Item if ($null -eq $item) { $item = $state.DataGrid.SelectedItem } if ($null -eq $item) { return } $values = foreach ($col in $state.Columns) { $item.$col } [System.Windows.Clipboard]::SetText($values -join "`t") }.GetNewClosure() $copyCellMenuItem.Add_Click({ & $state.CopyCurrentCell }.GetNewClosure()) $copyRowMenuItem.Add_Click({ & $state.CopyCurrentRow }.GetNewClosure()) $dataGrid.Add_PreviewKeyDown({ if ($_.Key -eq [System.Windows.Input.Key]::C -and ($_.KeyboardDevice.Modifiers -band [System.Windows.Input.ModifierKeys]::Control)) { & $state.CopyCurrentCell $_.Handled = $true } }.GetNewClosure()) $dataGrid.ItemsSource = $view # Case-insensitive substring. Global: any column. Per-column: AND, own column only. $state.Filter = $null $state.FilterPredicate = [Predicate[object]] { $row = $args[0] $needle = $state.Filter if (-not [string]::IsNullOrEmpty($needle)) { $globalMatch = $false foreach ($col in $state.Columns) { $value = $row.$col if ($null -ne $value -and $value.ToString().IndexOf($needle, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { $globalMatch = $true break } } if (-not $globalMatch -and $state.DetailsKey) { $value = $row.($state.DetailsKey) if ($null -ne $value -and $value.ToString().IndexOf($needle, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { $globalMatch = $true } } if (-not $globalMatch) { return $false } } foreach ($col in $state.Columns) { $colNeedle = $state.ColumnFilters[$col] if ([string]::IsNullOrEmpty($colNeedle)) { continue } $value = $row.$col if ($null -eq $value -or $value.ToString().IndexOf($colNeedle, [System.StringComparison]::OrdinalIgnoreCase) -lt 0) { return $false } } return $true }.GetNewClosure() $txtFilter.Add_TextChanged({ $text = $txtFilter.Text $state.TxtFilterHint.Visibility = if ([string]::IsNullOrEmpty($text)) { [System.Windows.Visibility]::Visible } else { [System.Windows.Visibility]::Collapsed } if ([string]::IsNullOrWhiteSpace($text)) { $state.Filter = $null } else { $state.Filter = $text } & $state.RefreshFilterState }.GetNewClosure()) if (-not $ViewOnly) { & $state.UpdateSelection # Visible rows only; count updates via Checked/Unchecked handlers. $state.SetAllVisible = { param([bool]$Value) foreach ($item in $state.View) { $item.IsSelected = $Value } $state.DataGrid.Items.Refresh() }.GetNewClosure() $btnSelectAll.Add_Click({ & $state.SetAllVisible $true }.GetNewClosure()) $btnDeselectAll.Add_Click({ & $state.SetAllVisible $false }.GetNewClosure()) # O(1) count update per checkbox toggle. $dataGrid.AddHandler( [System.Windows.Controls.Primitives.ToggleButton]::CheckedEvent, [System.Windows.RoutedEventHandler] { $state.SelectedCount++ & $state.UpdateSelection }.GetNewClosure() ) $dataGrid.AddHandler( [System.Windows.Controls.Primitives.ToggleButton]::UncheckedEvent, [System.Windows.RoutedEventHandler] { $state.SelectedCount-- & $state.UpdateSelection }.GetNewClosure() ) $btnOK.Add_Click({ $selected = [System.Collections.Generic.List[object]]::new() foreach ($item in $state.WrappedItems) { if ($item.IsSelected) { $selected.Add($item._OriginalItem) } } $state.DialogResult = $true $state.SelectedItems = $selected.ToArray() $state.Window.Close() }.GetNewClosure()) $btnCancel.Add_Click({ $state.DialogResult = $false $state.Window.Close() }.GetNewClosure()) } else { $txtSelectionInfo.Text = "$($wrappedItems.Count) item(s)" $btnOK.Add_Click({ $state.Window.Close() }.GetNewClosure()) } [void]$window.ShowDialog() if ($ViewOnly) { return } if ($state.DialogResult -eq $true) { return $state.SelectedItems } else { return $null } } } # SIG # Begin signature block # MIInigYJKoZIhvcNAQcCoIInezCCJ3cCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnpsgO96QI7yZF # TeNlFBl5Ku/Fq6eS8p5HeZOmzyz1M6CCIR0wggZFMIIELaADAgECAhAIMk+dt9qR # b2Pk8qM8Xl1RMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQK # ExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2Rl # IFNpZ25pbmcgMjAyMSBDQTAeFw0yNDA0MDQxNDA0MjRaFw0yNzA0MDQxNDA0MjNa # MGsxCzAJBgNVBAYTAk5MMRIwEAYDVQQHDAlTY2hpam5kZWwxIzAhBgNVBAoMGkpv # aG4gQmlsbGVrZW5zIENvbnN1bHRhbmN5MSMwIQYDVQQDDBpKb2huIEJpbGxla2Vu # cyBDb25zdWx0YW5jeTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMsl # ntDbSQwHZXwFhmibivbnd0Qfn6sqe/6fos3pKzKxEsR907RkDMet2x6RRg3eJkiI # r3TFPwqBooyXXgK3zxxpyhGOcuIqyM9J28DVf4kUyZHsjGO/8HFjrr3K1hABNUsz # P0o7H3o6J31eqV1UmCXYhQlNoW9FOmRC1amlquBmh7w4EKYEytqdmdOBavAD5Xq4 # vLPxNP6kyA+B2YTtk/xM27TghtbwFGKnu9Vwnm7dFcpLxans4ONt2OxDQOMA5Nwg # cUv/YTpjhq9qoz6ivG55NRJGNvUXsM3w2o7dR6Xh4MuEGrTSrOWGg2A5EcLH1XqQ # tkF5cZnAPM8W/9HUp8ggornWnFVQ9/6Mga+ermy5wy5XrmQpN+x3u6tit7xlHk1H # c+4XY4a4ie3BPXG2PhJhmZAn4ebNSBwNHh8z7WTT9X9OFERepGSytZVeEP7hgypt # SLcuhpwWeR4QdBb7dV++4p3PsAUQVHFpwkSbrRTv4EiJ0Lcz9P1HPGFoHiFAQQID # AQABo4IBeDCCAXQwDAYDVR0TAQH/BAIwADA9BgNVHR8ENjA0MDKgMKAuhixodHRw # Oi8vY2NzY2EyMDIxLmNybC5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNybDBzBggrBgEF # BQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0dHA6Ly9jY3NjYTIwMjEub2NzcC1jZXJ0 # dW0uY29tMDUGCCsGAQUFBzAChilodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv # Y2NzY2EyMDIxLmNlcjAfBgNVHSMEGDAWgBTddF1MANt7n6B0yrFu9zzAMsBwzTAd # BgNVHQ4EFgQUO6KtBpOBgmrlANVAnyiQC6W6lJwwSwYDVR0gBEQwQjAIBgZngQwB # BAEwNgYLKoRoAYb2dwIFAQQwJzAlBggrBgEFBQcCARYZaHR0cHM6Ly93d3cuY2Vy # dHVtLnBsL0NQUzATBgNVHSUEDDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4Aw # DQYJKoZIhvcNAQELBQADggIBAEQsN8wgPMdWVkwHPPTN+jKpdns5AKVFjcn00psf # 2NGVVgWWNQBIQc9lEuTBWb54IK6Ga3hxQRZfnPNo5HGl73YLmFgdFQrFzZ1lnaMd # Icyh8LTWv6+XNWfoyCM9wCp4zMIDPOs8LKSMQqA/wRgqiACWnOS4a6fyd5GUIAm4 # CuaptpFYr90l4Dn/wAdXOdY32UhgzmSuxpUbhD8gVJUaBNVmQaRqeU8y49MxiVrU # KJXde1BCrtR9awXbqembc7Nqvmi60tYKlD27hlpKtj6eGPjkht0hHEsgzU0Fxw7Z # JghYG2wXfpF2ziN893ak9Mi/1dmCNmorGOnybKYfT6ff6YTCDDNkod4egcMZdOSv # +/Qv+HAeIgEvrxE9QsGlzTwbRtbm6gwYYcVBs/SsVUdBn/TSB35MMxRhHE5iC3aU # TkDbceo/XP3uFhVL4g2JZHpFfCSu2TQrrzRn2sn07jfMvzeHArCOJgBW1gPqR3Wr # J4hUxL06Rbg1gs9tU5HGGz9KNQMfQFQ70Wz7UIhezGcFcRfkIfSkMmQYYpsc7rfz # j+z0ThfDVzzJr2dMOFsMlfj1T6l22GBq9XQx0A4lcc5Fl9pRxbOuHHWFqIBD/BCE # hwniOCySzqENd2N+oz8znKooSISStnkNaYXt6xblJF2dx9Dn89FK7d1IquNxOwt0 # tI5dMIIGgjCCBGqgAwIBAgIQNsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQwF # ADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcT # C0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAs # BgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcN # MjEwMzIyMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBXMQswCQYDVQQGEwJHQjEYMBYG # A1UEChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBU # aW1lIFN0YW1waW5nIFJvb3QgUjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAiJ3YuUVnnR3d6LkmgZpUVMB8SQWbzFoVD9mUEES0QUCBdxSZqdTkdizI # CFNeINCSJS+lV1ipnW5ihkQyC0cRLWXUJzodqpnMRs46npiJPHrfLBOifjfhpdXJ # 2aHHsPHggGsCi7uE0awqKggE/LkYw3sqaBia67h/3awoqNvGqiFRJ+OTWYmUCO2G # AXsePHi+/JUNAax3kpqstbl3vcTdOGhtKShvZIvjwulRH87rbukNyHGWX5tNK/WA # BKf+Gnoi4cmisS7oSimgHUI0Wn/4elNd40BFdSZ1EwpuddZ+Wr7+Dfo0lcHflm/F # DDrOJ3rWqauUP8hsokDoI7D/yUVI9DAE/WK3Jl3C4LKwIpn1mNzMyptRwsXKrop0 # 6m7NUNHdlTDEMovXAIDGAvYynPt5lutv8lZeI5w3MOlCybAZDpK3Dy1MKo+6aEtE # 9vtiTMzz/o2dYfdP0KWZwZIXbYsTIlg1YIetCpi5s14qiXOpRsKqFKqav9R1R5vj # 3NgevsAsvxsAnI8Oa5s2oy25qhsoBIGo/zi6GpxFj+mOdh35Xn91y72J4RGOJEoq # zEIbW3q0b2iPuWLA911cRxgY5SJYubvjay3nSMbBPPFsyl6mY4/WYucmyS9lo3l7 # jk27MAe145GWxK4O3m3gEFEIkv7kRmefDR7Oe2T1HxAnICQvr9sCAwEAAaOCARYw # ggESMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBT2 # d2rdP/0BE/8WoWyCAi/QCj0UJTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw # AwEB/zATBgNVHSUEDDAKBggrBgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAwUAYD # VR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVz # dFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMDUGCCsGAQUFBwEBBCkwJzAl # BggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0B # AQwFAAOCAgEADr5lQe1oRLjlocXUEYfktzsljOt+2sgXke3Y8UPEooU5y39rAARa # AdAxUeiX1ktLJ3+lgxtoLQhn5cFb3GF2SSZRX8ptQ6IvuD3wz/LNHKpQ5nX8hjsD # LRhsyeIiJsms9yAWnvdYOdEMq1W61KE9JlBkB20XBee6JaXx4UBErc+YuoSb1SxV # f7nkNtUjPfcxuFtrQdRMRi/fInV/AobE8Gw/8yBMQKKaHt5eia8ybT8Y/Ffa6HAJ # yz9gvEOcF1VWXG8OMeM7Vy7Bs6mSIkYeYtddU1ux1dQLbEGur18ut97wgGwDiGin # CwKPyFO7ApcmVJOtlw9FVJxw/mL1TbyBns4zOgkaXFnnfzg4qbSvnrwyj1NiurMp # 4pmAWjR+Pb/SIduPnmFzbSN/G8reZCL4fvGlvPFk4Uab/JVCSmj59+/mB2Gn6G/U # YOy8k60mKcmaAZsEVkhOFuoj4we8CYyaR9vd9PGZKSinaZIkvVjbH/3nlLb0a7SB # IkiRzfPfS9T+JesylbHa1LtRV9U/7m0q7Ma2CQ/t392ioOssXW7oKLdOmMBl14su # VFBmbzrt5V5cQPnwtd3UOTpS9oCG+ZZheiIvPgkDmA8FzPsnfXW5qHELB43ET7HH # FHeRPRYrMBKjkb8/IN7Po0d0hQoF4TeMM+zYAJzoKQnVKOLg8pZVPT8wgganMIIE # j6ADAgECAhEAkKwIciD9xafEa1zHDfc9BjANBgkqhkiG9w0BAQwFADBXMQswCQYD # VQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0 # aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFJvb3QgUjQ2MB4XDTI2MDMyNTAwMDAw # MFoXDTQxMDMyNDIzNTk1OVowVTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3Rp # Z28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGlu # ZyBDQSBSNDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCu5EqiAa2C # HGL5Zi1bmgPM8NUXwYZJ+BtQqHps43GLTC+sjVLypsBh+8uv+TLkgtVGD//vSmA0 # qrzELf9YRCh2MTAA/aGaQZKGg0BRCmziR3pbCnvgWjtGXBDUyn3j3K2lZAO8KxgF # tlxwOYEAkL+CCqK4v9zzTl8ZwzDpPMiDIFa5THk8an1ieF5I09cXNrPQw+1ER1li # ThaG0z6FrOpqwxZWmPRZQBw2E32878UB1bL0Zp91vuWZgsMpNNiPCoBj0/1F+LE8 # +NRokfqacFI0F2tftrRB2W7HQClLR9zjxFbWb5be2rceIfNyHUUfKGIvMI2NzoxS # lxXnFqUG887D8W1Cj8DFok688JKxWvHR/9aQykSbd+9Vutj36ij2sgq/125wTpUZ # /AgC0ph50bRs7gFrUyaXE9wSsOqMvCCC+sEm7vd/BemSG0TSHNXSmyCba+FCzeke # WX03TRIcF3Laqd0Rw24OH7jpei4zaGhcI7nfdhBA4c8RScxNY6jeHLHHmSMMTk9W # qn7H4dLhUBP5YEwbgbN4uv1i9ltTnHli8t1xHV0StX9BFgrnmunTX19kUXY1H5OR # JbRZyZDdvm1oZyteDj0SnMozr+YSmdIleDUTXdfoY7b2taz8s2+QbOxLxcahEIYG # Wzqu6h955tKwcANHcZ4gTmAhT3btuOiQsQIDAQABo4IBbjCCAWowHwYDVR0jBBgw # FoAU9ndq3T/9ARP/FqFsggIv0Ao9FCUwHQYDVR0OBBYEFDp0pQxnxkJQwv21/Me7 # KTSC9Hq5MA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1Ud # JQQMMAoGCCsGAQUFBwMIMCMGA1UdIAQcMBowCAYGZ4EMAQQCMA4GDCsGAQQBsjEB # AgEDCDBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLnNlY3RpZ28uY29tL1Nl # Y3RpZ29QdWJsaWNUaW1lU3RhbXBpbmdSb290UjQ2LmNybDB8BggrBgEFBQcBAQRw # MG4wRwYIKwYBBQUHMAKGO2h0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1 # YmxpY1RpbWVTdGFtcGluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8v # b2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMt5SR2bxngNm+N8o # c6Gq76Gx1c235fkX7jw8Ho9MAkJGADerHE7dhsBXttqmzgr/7ZZahZSykGRPhPY1 # crj028kB8KzO0dKC2qQBAwtfgqMLKkkX/6bYq2uT33eD6ByAp2/XKD0LcmZh0kKe # cvSBr6ln9ajX6u1dnx2fA7xEKy1M3qBhfQSUWLtjs2nFt0ELVLptzTlX9ID0cL+i # OPfdboZ3CelT+JXKVKR2Sge0d4YiFAtPZkfSo8z1Z1x7y/Z9mwMIlBAnyuWXs4Ys # NuxdrYIt/QxE31PDOJ9DesS4Bc7H9OTORlEV/AvfiF/VepKZpira1MzLYuCw+uoL # Zn/pkpvd+CvNTS+mEHjBJNa6WK1j8qXFu+jIq+sG9QILHiyB6p/xpHrkJu8zkw39 # 3+VqF9eKlTY2VjRxdycZLrVemZ4Yp3wi33b+W58CllH3HqjmowlZ7SOrgmx8YwYO # kgrHsXOQHyBp6O4FRb8In0+FzjT7ElGie9V7CfhL3IlVFZ4zjuKsZtH1iU3fGu4z # /JnOGT6sCb0BbTqe/uhvpFCQBdH5xPGIA/LrbQUXjU2tWJgHhTIqnN/HvHyOHi5t # M4zP3nhgh2rJ6Kqq2xsHBeNYs/R18xQ8DeIg+c90Eoaeh0YlN1KU8AyYol3K9M+q # Y5ez8syd/7ZlrRnoVewgH3P1pcswgga5MIIEoaADAgECAhEAmaOACiZVO2Wr3G6E # prPqOTANBgkqhkiG9w0BAQwFADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVu # aXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZp # Y2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y # ayBDQSAyMB4XDTIxMDUxOTA1MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkGA1UE # BhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UE # AxMbQ2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAnSPPBDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTmflN4 # mSAfgLKTvggv24/rWiVGzGxT9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbvIKck # 6+hI1shsylP4JyLvmxwLHtSworV9wmjhNd627h27a8RdrT1PH9ud0IF+njvMk2xq # bNTIPsnWtw3E7DmDoUmDQiYi/ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzuC8KN # qfcYf7Z4/iZgkBJ+UFNDcc6zokZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD2bSl # bfsq7BiqljjaCun+RJgTgFRCtsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QOZgYq # VWQ4x6cM7/G0yswg1ElLlJj6NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkcSkAE # xzd4prHwYjUXTeZIlVXqj+eaYqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDYO962 # WZayx7ACFf5ydJpoEowSP07YaBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq1UiY # PIAHlezf4muJqxqIns/kqld6JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBSND36 # E/ENVv8urPS0yOnpG4tIoBGxVCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JSqPEC # AwEAAaOCAVUwggFRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA23uf # oHTKsW73PMAywHDNMB8GA1UdIwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79MA4G # A1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAnMCWg # I6Ahhh9odHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUFBwEB # BGAwXjAoBggrBgEFBQcwAYYcaHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNvbTAy # BggrBgEFBQcwAoYmaHR0cDovL3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNhMi5j # ZXIwOQYDVR0gBDIwMDAuBgRVHSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cu # Y2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q7EP8 # 9DttyZqGYn2XRDhJkL6P+/T0IPZyxfxiXumYlARMgwRzLRUStJl490L94C9LGF3v # jzzH8Jq3iR74BRlkO18J3zIdmCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLUUAsO # 0eqeLNhLVsgw6/zOfImNlARKn1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXdd3x2 # mbJCKKtl2s42g9KUJHEIiLni9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rNLqyA # 3ahe8WlxVWkxyrQLjH8ItI17RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMcZNhp # n8zJ+6MTyE6YoEBSRVrs0zFFIHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vUBfSo # uCReZwSLo8WdrDlPXtR0gicDnytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTpV2Na # 3PR7nxYVlPu1JPoRZCbH86gc96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGgK2W1 # eEJfo2qyrBNPvF7wuAyQfiFXLwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2ywzOr+ # tKyEVAotnyU9vyEVOaIYMk3IeBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602NCmv # O1nm+/80nLy5r0AZvCQxaQ4wggbiMIIEyqADAgECAhEA507yVbBQT/rbpt/3/Iuj # FTANBgkqhkiG9w0BAQwFADBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGln # byBMaW1pdGVkMSwwKgYDVQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5n # IENBIFI0MTAeFw0yNjAzMjUwMDAwMDBaFw0zNzA2MjQyMzU5NTlaMHIxCzAJBgNV # BAYTAkdCMRcwFQYDVQQIEw5HcmVhdGVyIExvbmRvbjEYMBYGA1UEChMPU2VjdGln # byBMaW1pdGVkMTAwLgYDVQQDEydTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5n # IFNpZ25lciBSMzcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCy/8Nt # S9xQ2UUtBRF32bj7VK3n4m50Uqjk/zTciSziYV40H1LKah0/oEklYG42E4VCP3Dv # sBUB6DmpCkDZ0jCnZBPIEevaH15ZJOQwFWP2ZXr5YjlJpb68Nlbs+ElNvKx32/1Y # Hde3qqUSLybjulxPLz6T85+HOIqK7M1Bep8LspyhEP/q6nw5kGxTSrGvufmeH+JF # 8CnVBcVMFA40FlIYh0cDJVFhhfTfdWgLy/vWuLMQoKkf3s/FvByf16r0rtbyHm/i # emwxSioJL9zyZDDKUNAbHXl0dhXo2VxUV2NcPXWXuoKsjL+6cfk6Vm2DHnxAlFdF # saBDIF1JOkSnC6PeLlBznZn2buF3vIIYJcq6N/zeFRCk4/HXDz7zgRsRRMdUB+rh # yk5FoZaBjw0nLq3GZ3fClLUx5es5pUAxzNODMBn7JkFYip2BAGBPER5eV0ROhk6t # GTG+fUiMiV+vgjg1YnP5FvnYWyEtWeQD/B2hp3vz0RvtdkM0p3igyadzrfpOBq5p # pVk/YsuhTQkP99ivneHAGfi5e7lmxJ+meoBPrRLuzMmb81rzzbESjJHMsn5RVtc6 # Ucs7rcMqQC13PUIO7BbGBETV2ufCmV6lPTp3P7XJOvmnUCRTPbVvMTpxP/z+SOHg # 4/OCBhiqs4FA9+4oQvlkk9w32NGASli9GWrm5wIDAQABo4IBjjCCAYowHwYDVR0j # BBgwFoAUOnSlDGfGQlDC/bX8x7spNIL0erkwHQYDVR0OBBYEFGEQ6XoSr1HEhdTy # z6R0D1DNIK/4MA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB # /wQMMAoGCCsGAQUFBwMIMEoGA1UdIARDMEEwCAYGZ4EMAQQCMDUGDCsGAQQBsjEB # AgEDCDAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29tL0NQUzBKBgNV # HR8EQzBBMD+gPaA7hjlodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJs # aWNUaW1lU3RhbXBpbmdDQVI0MS5jcmwwegYIKwYBBQUHAQEEbjBsMEUGCCsGAQUF # BzAChjlodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3Rh # bXBpbmdDQVI0MS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28u # Y29tMA0GCSqGSIb3DQEBDAUAA4ICAQAD6j2N0azN+hl6k6bKB5/U6VuSOs93ZBb3 # Pczy9VtBIKu4947Z5GwL0aFngIxl+GSuLFrJgPruBCRvKJEJsm7kv+LQ1COVCEG9 # tZ+IRtr4ocUoa53lgdFaENlS0N4wgkZkbQEPv+x+1lSjYh+T4JeL9mUznT7Erc6S # p5dWLka5sMP/m3GZi6oJPdPcsCKWagH7m2H2xDGIyHJC5PdH9phvi/KmhkktiSVT # NNqVeV5bWdX2zhRE6UTfz0IcMoCL996lFIydXxOCE4MNDHDM0as4lnTiT/KHMccO # 6l8c9TnUVgmpci9ar1IABZ2U1XUkYjGGSn9MC3EHDP9V39VuBVvZ33/BEV/EWSRr # f07T7jFplKX+gQr/UOqPGMlE7ZJ72UaUkNJy7bVl3bcLKzdpjIHzLkf/4MVa1V7w # 8wqCv5W4gOnRGTlud5UMARbRM8BPxR/CXYXoMmIOD8pmTk2axgRL4LG8XtuchISd # CHRmtacAmLGq5XSYSVTHTXADlO48iDKh3HM2r98LSF6f0sG12d8V9Jn7C3wDUieO # xuKj4MdWrW+hiJU2kF87v6eH00HgCFFc2V0+CvfOCMn7juzS41jLaINcBlKWQ/fK # b/uDLfWOW73z1I2lFY7Xj8tQ1XYtK5eREjWItM8jpl1cbQOc88btR+0XS2TmboE/ # 141+va2PWzGCBcMwggW/AgEBMGowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFz # c2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2ln # bmluZyAyMDIxIENBAhAIMk+dt9qRb2Pk8qM8Xl1RMA0GCWCGSAFlAwQCAQUAoIGE # MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQB # gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE # MSIEID+OgPBsYML35zRIPgn3n77fDmnBb1OwUoj1mvGe0dALMA0GCSqGSIb3DQEB # AQUABIIBgHsY8K0RZW0+HkzO94q+MI1lyNj06HR5iiaW0XlBus6DSntksBO76OmA # wEC1063e1WP9FI/jLAJ6J8vFkmOGTjUPHUwQxVN2m6wGAjlRXL0emlIXK43GwIsZ # WL676+M45yUU/wdPh0ejuM5/Fdt+xawVC8njZF79gemgeuFWQ/dJnxRH8/rjnidA # oZS6JDoFhs1RKWjngQmy2So8qbEytuwi9QHwMjpe2jQxS9bseeBZwMHiB5Cqpf28 # IOXkDnP/pTlwxWpJRP+pX77upfGIrbNNtm+S9DjKjebxl9g9r3ahNkqOnLcpf7lE # Icd1s03R5zxSvevF+hXOqfz9ZVcYg/dwfijwZyAB9194Cj6bPd7cILvRRU8nEokw # ILmWJyl0lAsgUgioiefV7NaFK01wpdnoxq+fEjQJve2RsPEG3O49KlkzRnP+XnzP # 456Fi8eKol9J5qz802fSNC9a+7HSf/P5zXt7qS1yTDl5pUwp4mpiKnysIgRSSxs2 # 6PODH9H7KaGCAyMwggMfBgkqhkiG9w0BCQYxggMQMIIDDAIBATBqMFUxCzAJBgNV # BAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3Rp # Z28gUHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjQxAhEA507yVbBQT/rbpt/3/Iuj # FTANBglghkgBZQMEAgIFAKB5MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJ # KoZIhvcNAQkFMQ8XDTI2MDcyMDA4NDE1MFowPwYJKoZIhvcNAQkEMTIEMNWUOdVT # aCjVIUGVheJ4L1/sDmcSdGeQr3lXKH/s235OVjhQP3mfcZoMdwYk31U5lDANBgkq # hkiG9w0BAQEFAASCAgAQHJr9lh6c6ijwC8U45R0PMR4BC+1gVN79g7PH97PLbewY # ljyZ8GWmKMeOuZGErLdNTJN3TgK/TugcONga5fJ26LPm3I4meJXu4E+4a1gXk9Us # RJ1/B5Tsp9XJFPBBrIirqNWPTgEI0RrUHsCM+1Y0cgaT2GlFL3OCMXu06X+Xj5Uf # z4rjY7VBaKna0111PN43PoD271H4HJfFbx9C5yCdtmPD65cHaAqlFG/zYflzzOQ0 # 4820OKfOCdfvqyPSSVwQ3nzGdXaZCjNyc2xzInEYTkJ+Omoi2rEcZ5PovCFCjqtV # 4PybJqQXjjBNshM3c517V/bV5B01bBtmoVbslZ95kCU/gfoUx0H0WRHb7/qAv36b # bbU6RnNDdyyFTdttUEXVieyHf6O4Cw14U3zaQmuA0HfWhFSWkxwrPEN4GXjRGtki # cLU4O39IXDxr1bkUw5NQnEOXCVzLUuWLkD03lB9wgi5qwzGVyoVJ7x01mYdF08Mb # ezzd5GdlVIFZhCVLW02IB41CDpajRImOO44zWj4NWPEM4RW0DVNOiDN2aREQ8uaR # EfBungoHd6ALuYSqqlz+FGzsvth/IN/1zqNSdm3XrRnY0NJD9F/cA0HRvu+iNNYO # ZMr5i13o/xkrZIwoF8OeLvLQfx9ibcJ90x/YjXMREyCmrYtXwAEfQOzkZDPKEQ== # SIG # End signature block |