GuiMyPS.psm1
<#
.SYNOPSIS Adds a Click event handler to every button within a given element. .DESCRIPTION The Add-ClickToEveryButton function traverses the visual tree of a given element and adds a Click event handler to every button found. Specific buttons can be ignored based on their names. .PARAMETER Element The root element to start the search. .PARAMETER ClickHandler The Click event handler to be added to each button. .EXAMPLE Add-ClickToEveryButton -Element $window -ClickHandler $ClickHandler Traverses the visual tree of the $window element and adds the $ClickHandler Click event handler to every button found. .NOTES Author: Brooks Vaughn Date: 2025-02-18 14:22:52 #> Function Add-ClickToEveryButton { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] $Element, [Parameter(Mandatory = $false)] [System.Windows.RoutedEventHandler]$ClickHandler ) # Dump Parameter Values # $PSBoundParameters.GetEnumerator() | ForEach-Object { # Write-Verbose "$($_.Key) = $($_.Value)" # } If ($Element -is 'System.Windows.Controls.Button') { Write-Verbose ("Add-ClickToEveryButton() Button Object: [{0}] - {1}" -f $Element.GetType().ToString(), $Element.Name) If (-not [String]::IsNullOrEmpty($ClickHandler)) { Write-Verbose (" Adding Click Event For: {0}" -f $Element.Content) $Element.Add_Click($ClickHandler) } } Else { Write-Verbose ("Add-ClickToEveryButton() Object: [{0}] - {1}" -f $Element.GetType().ToString(), $Element.Name) } If ($Element.HasItems -or $Element.HasContent -or $Element.Child.Count -gt 0 -or $Element.Children.Count -gt 0 -or $Element.Items.Count -gt 0) { # The logical tree can contain any type of object, not just # instances of DependencyObject subclasses. LogicalTreeHelper # only works with DependencyObject subclasses, so we must be # sure that we do not pass it an object of the wrong type. $depObj = $Element If ($null -ne $depObj) { ForEach ($logicalChild in ([System.Windows.LogicalTreeHelper]::GetChildren($depObj))) { Add-ClickToEveryButton -Element $logicalChild -ClickHandler:$ClickHandler } } } } <# .SYNOPSIS Adds a Click event handler to every menu item within a given menu object. .DESCRIPTION The Add-ClickToEveryMenuItem function traverses the visual tree of a given menu object and adds a Click event handler to every menu item found. Specific menu items can be ignored based on their properties. .PARAMETER MenuObj The root menu object to start the search. .PARAMETER Handler The Click event handler to be added to each menu item. .EXAMPLE Add-ClickToEveryMenuItem -MenuObj $menu -Handler $handler Traverses the visual tree of the $menu object and adds the $handler Click event handler to every menu item found. .NOTES Author: Brooks Vaughn Date: 2025-02-18 14:22:52 #> Function Add-ClickToEveryMenuItem { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] $MenuObj, [Parameter(Mandatory = $true)] [ScriptBlock]$Handler ) # Dump Parameter Values $PSBoundParameters.GetEnumerator() | ForEach-Object { Write-Verbose "$($_.Key) = $($_.Value)" } If ([String]::IsNullOrEmpty($Handler)) { Write-Warning "Parameter -Handler `$Handler cannot be blank" Return } If ([String]::IsNullOrEmpty($MenuObj)) { Write-Warning "Parameter -MenuObj `$MenuObj cannot be blank" Return } Write-Verbose ("Menu Object: [{0}] - {1}" -f $MenuObj.Name, $MenuObj.GetType().ToString()) ForEach ($child in $MenuObj.Items) { Write-Verbose (" {0} - [{1}]" -f $child.Header, $child.GetType().ToString()) If ($child.HasItems) { Add-ClickToEveryMenuItem -MenuObj $child -Handler:$Handler } Else { If ($child -is 'System.Windows.Controls.MenuItem') { If (-not [String]::IsNullOrEmpty($Handler)) { Write-Verbose (" Adding Click Event For: {0}" -f $child.Content) $child.Add_Click($Handler) } Else { $child.Add_Click($null) } } } } } <# .SYNOPSIS Adds event handlers to every checkbox within a given element. .DESCRIPTION The Add-EventsToEveryCheckBox function traverses the visual tree of a given element and adds event handlers to every checkbox found. Specific event handlers can be added based on the properties of the checkboxes. .PARAMETER Element The root element to start the search. .PARAMETER ClickHandler The Click event handler to be added to each checkbox. .PARAMETER CheckedHandler The Checked event handler to be added to each checkbox. .PARAMETER UncheckedHandler The Unchecked event handler to be added to each checkbox. .PARAMETER PreviewMouseUpHandler The PreviewMouseUp event handler to be added to each TreeViewItem. .PARAMETER PreventSelectionScrolling A switch to prevent horizontal content scrolling when an item is clicked. .EXAMPLE Add-EventsToEveryCheckBox -Element $window -ClickHandler $clickHandler Traverses the visual tree of the $window element and adds the $clickHandler Click event handler to every checkbox found. .NOTES Author: Brooks Vaughn Date: 2025-02-18 14:22:52 #> Function Add-EventsToEveryCheckBox { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] $Element, [Parameter(Mandatory = $false)] [System.Windows.RoutedEventHandler]$ClickHandler = $null, [Parameter(Mandatory = $false)] [System.Windows.RoutedEventHandler]$CheckedHandler = $null, [Parameter(Mandatory = $false)] [System.Windows.RoutedEventHandler]$UncheckedHandler = $null, [Parameter(Mandatory = $false)] [System.Windows.Input.MouseButtonEventHandler]$PreviewMouseUpHandler = $null, [Switch]$PreventSelectionScrolling ) # Dump Parameter Values # $PSBoundParameters.GetEnumerator() | ForEach-Object { # Write-Verbose "$($_.Key) = $($_.Value)" # } Write-Verbose ("Add-EventsToEveryCheckBox(): [{0}] : ({1})" -f $($Element.GetType().ToString()), $($Element.Name)) If ($Element -is 'System.Windows.Controls.TreeViewItem') { $Element.IsExpanded = $true # "True" If ($null -ne $PreviewMouseUpHandler) { $Element.Add_PreviewMouseUp($PreviewMouseUpHandler) } If ($PreventSelectionScrolling) { # This prevents horizontal content scrolling when an item is clicked $Element.Add_RequestBringIntoView({ Param( [object]$theSender, [System.Windows.RequestBringIntoViewEventArgs]$e ) Write-Verbose ("`$Element.Add_RequestBringIntoView {0}: {1}({2})" -f $theSender.Name, $e.Source.Name, $e.ToString()) # Mark the event as handled $e.Handled = $true }) } <# $Count = [System.Windows.Media.VisualTreeHelper]::GetChildrenCount($Element) For ($i=0; $i -lt $Count; $I++) { $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, $i); Add-EventsToEveryCheckBox -Element $current -ClickHandler:$ClickHandler -CheckedHandler:$CheckedHandler -UncheckedHandler:$UncheckedHandler } #> Write-Verbose (" Header Name: {0}, Type: {1}" -f $Element.Header.Type, $Element.Header.Name) <# If ($Element.Header.Type -eq 'Server') { $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, 0); Add-EventsToEveryCheckBox -Element $current -ClickHandler:$ClickHandler -CheckedHandler:$CheckedHandler -UncheckedHandler:$UncheckedHandler -PreviewMouseUpHandler:$PreviewMouseUpHandler } #> } ElseIf ($Element -is 'System.Windows.Controls.TextBlock') { Write-Verbose (" TextBlock: {0}" -f $($Element.Inlines.Text | Out-String)) } ElseIf ($Element -is 'System.Windows.Controls.CheckBox') { Write-Verbose (" Adding CheckBox Event Handlers For: " + $Element.Content.Inlines.text -join "") If (-not [Sting]::IsNullOrEmpty($ClickHandler)) { $Element.Add_Click($ClickHandler) } If (-not [Sting]::IsNullOrEmpty($CheckedHandler)) { $Element.Add_Checked($CheckedHandler) } If (-not [Sting]::IsNullOrEmpty($UncheckedHandler)) { $Element.Add_Unchecked($UncheckedHandler) } } $Count = [System.Windows.Media.VisualTreeHelper]::GetChildrenCount($Element) For ($i=0; $i -lt $Count; $i++) { $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, $i) Add-EventsToEveryCheckBox -Element $current -ClickHandler:$ClickHandler -CheckedHandler:$CheckedHandler -UncheckedHandler:$UncheckedHandler -PreviewMouseUpHandler:$PreviewMouseUpHandler } } <# .SYNOPSIS Generates Event Handler Code Snippet for the desired controlType. .DESCRIPTION The Build-HandlerCode function generates an event handler code snippet for the specified control type. $Elements is an array of [PSCustomObject] created by the Find-EveryControl function. $ControlType is the type of elements in $Elements and is used mostly for naming the handler. .PARAMETER Elements An array of [PSCustomObject] representing the elements created by Find-EveryControl() function. [PSCustomObject]@{ Name = $Element.Name Type = $Element.GetType().ToString() Text = (Get-ControlContent -Element $Element) Element = $Element } .PARAMETER ControlType Is the type of elements in $Elements and is used mostly for naming of the handler .EXAMPLE $elements = @() $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' Build-HandlerCode -Elements $elements -ControlType System.Windows.Controls.MenuItem .NOTES Author: Brooks Vaughn Date: 2025-02-19 18:12:04 #> Function Build-HandlerCode { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [System.Object[]]$Elements, [Parameter(Mandatory = $true)] [String]$ControlType ) If ($null -eq $Elements) { Write-Warning "Parameter -Elements is empty or missing" Return } If ([String]::IsNullOrEmpty($ControlType)) { Write-Host "Parameter -ControlType is empty or missing" -ForegroundColor Red Return } $eol = [System.Environment]::NewLine $sb = [System.Text.StringBuilder]::new() $handlerName = "`$handler_$($ControlType.Split('.')[-1])_Click" # Starting Code [void]$sb.AppendLine(@" $handlerName = { Param ([object]`$theSender, [System.EventArgs]`$e) Write-Host ("``$handlerName() Item clicked: {0}" -f `$theSender.Name) Switch -Regex (`$theSender.Name) { "@ + $eol) # Body Code consists of Regex patterns of the Item Element Name it finds of matching ControlTypes ForEach ($element in $Elements) { If (-not [String]::IsNullOrEmpty($element.Name)) { [void]$sb.AppendLine(@" '^$($element.Name)$' { Break } "@) } } # Ending Code [void]$sb.AppendLine(@" default { Write-Host ("{0}: {1}({2})" -f `$theSender.Name, `$e.OriginalSource.Name, `$e.OriginalSource.ToString()) } } } "@) $sb.ToString() } <# Usage Example # > $elements = @() $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' Build-HandlerCode -Elements $elements -ControlType 'System.Windows.Controls.MenuItem' # How to use the same $elements list to Add_Click() Events $elements.ForEach({$_.Element.Add_Click($handler_MenuItem_Click)}) #> <# .SYNOPSIS Finds every control of a specified type within a given element. .DESCRIPTION The Find-EveryControl function traverses the visual or logical tree of a given element and finds every control of a specified type. Specific controls can be excluded based on their properties. .PARAMETER ControlType The type of control to Gather .PARAMETER Element The root element to start searching from .PARAMETER ExcludeElement A switch to exclude attaching the element to the results. .PARAMETER UseVisualTreeHelper A switch to use VisualTreeHelper for the search. .PARAMETER IncludeAll A switch to include all controls in the search results. .EXAMPLE Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' Finds every ToggleButton control within the $form element. .EXAMPLE Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement -UseVisualTreeHelper -IncludeAll Finds all controls (-IncludeAll) using VisualTreeHelper. Best use after exiting ShowDialog(). Excludes attaching Element (-ExcludeElement) to result .EXAMPLE $elements = @() $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' $elements.ForEach({$_.Element.Add_Click($handler_MenuItem_Click)}) Find all MenuItem and ToggleButton elements and Add Click Event Handler .EXAMPLE $elements = @() $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' -ExcludeElement $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement Build-HandlerCode -Elements $elements -ControlType System.Windows.Controls.MenuItem Find all MenuItem and ToggleButton elements and Generate the code for the Click Event Handler .NOTES Author: Brooks Vaughn Date: 2025-02-19 14:29:36 #> Function Find-EveryControl { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] $ControlType, [Parameter(Mandatory = $true)] $Element, [Parameter(Mandatory = $false)] [Switch]$ExcludeElement, [Parameter(Mandatory = $false)] [Switch]$UseVisualTreeHelper, [Parameter(Mandatory = $false)] [Switch]$IncludeAll ) If (-not [String]::IsNullOrEmpty($IncludeAll) -and $IncludeAll) { $includeElement = $null If (-not $ExcludeElement -or [String]::IsNullOrEmpty($ExcludeElement)) { $includeElement = $Element } [PSCustomObject]@{ Name = $Element.Name Type = $Element.GetType().ToString() Text = (Get-ControlContent -Element $Element) Element = $includeElement } } Else { If ($Element -is $ControlType) { $includeElement = $null If (-not $ExcludeElement -or [String]::IsNullOrEmpty($ExcludeElement)) { $includeElement = $Element } [PSCustomObject]@{ Name = $Element.Name Type = $Element.GetType().ToString() Text = (Get-ControlContent -Element $Element) Element = $includeElement } } } If ($UseVisualTreeHelper) { $Count = [System.Windows.Media.VisualTreeHelper]::GetChildrenCount($Element) If ($Count -gt 0) { For ($i=0; $i -lt $Count; $i++) { $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, $i) Find-EveryControl -Element $current -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement -UseVisualTreeHelper:$UseVisualTreeHelper } } } Else { If ($Element.HasContent) { ForEach ($item in $Element.Content) { Find-EveryControl -Element $item -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement } } If ($Element.Children) { ForEach ($child in $Element.Children) { Find-EveryControl -Element $child -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement } } If ($Element.HasItems) { ForEach ($item in $Element.Items) { Find-EveryControl -Element $item -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement } } } } # Example usage: # Find-EveryControl -Element $WPF_menuMasterDataGrid -ControlType 'System.Windows.Controls.MenuItem' # Find-EveryControl -Element $WPF_menuDetailDataGrid -ControlType 'System.Windows.Controls.MenuItem' # Find-EveryControl -Element $WPF_tabControl -ControlType 'System.Windows.Controls.Button' # Find-EveryControl -Element $WPF_gridSqlQueryEditor -ControlType 'System.Windows.Controls.Primitives.ToggleButton' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' # Find-EveryControl -Element $WPF_tabControl -ControlType 'System.Windows.Controls.Primitives.ToggleButton' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.RadioButton' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Combobox' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Button' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -IncludeAll -ExcludeElement -UseVisualTreeHelper # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -IncludeAll -ExcludeElement # How to identify all controls (-IncludeAll) from VisualTreeHelper. Use after exiting ShowDialog() # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement -UseVisualTreeHelper -IncludeAll # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement -UseVisualTreeHelper # Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement <# .SYNOPSIS Locates the root element (main form) of a control. .DESCRIPTION This function traverses the visual tree to find the root element of a given control. .PARAMETER Element The control for which to find the root element. .EXAMPLE $form = Find-RootElement -Element $Button .NOTES Author: Brooks Vaughn Date: 2025-02-19 14:29:36 #> Function Find-RootElement { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Windows.DependencyObject] $Element ) While ($Parent = [System.Windows.Media.VisualTreeHelper]::GetParent($Element)) { $Element = $Parent } Return $Element } <# .SYNOPSIS Formats an XML string with indentation and optional attribute formatting and XML declaration. .DESCRIPTION The Format-XML function formats an XML string with a specified indentation level. It provides options to format attributes with new lines and to include or omit the XML declaration. .PARAMETER xml The XML string to be formatted. .PARAMETER indent The number of spaces to use for indentation. Default is 4. .PARAMETER FormatAttributes A switch to format attributes with new lines. .PARAMETER IncludeXmlDeclaration A switch to include the XML declaration. .EXAMPLE Format-XML -xml $inputXML -indent 4 Formats the XML string $inputXML with an indentation level of 4 spaces. .EXAMPLE Format-XML -xml $inputXML -indent 4 -FormatAttributes Formats the XML string $inputXML with an indentation level of 4 spaces and formats attributes with new lines. .EXAMPLE Format-XML -xml $inputXML -indent 4 -IncludeXmlDeclaration Formats the XML string $inputXML with an indentation level of 4 spaces and includes the XML declaration. .NOTES Author: Brooks Vaughn Date: 2025-02-18 14:22:52 #> function Format-XML { [CmdletBinding()] Param ( [parameter(Mandatory=$true,ValueFromPipeLine=$true)] [xml]$xml, [parameter(Mandatory=$false,ValueFromPipeLine=$false)] $indent = 4, [switch]$FormatAttributes, [switch]$IncludeXmlDeclaration ) Process { $StringWriter = New-Object System.IO.StringWriter $XmlWriterSettings = New-Object System.Xml.XmlWriterSettings $XmlWriterSettings.Indent = $true $XmlWriterSettings.IndentChars = " " * $indent If (-not [Sting]::IsNullOrEmpty($IncludeXmlDeclaration)) { $XmlWriterSettings.OmitXmlDeclaration = $true } If (-not [Sting]::IsNullOrEmpty($FormatAttributes)) { $XmlWriterSettings.NewLineOnAttributes = $true } $XmlWriter = [System.Xml.XmlWriter]::Create($StringWriter, $XmlWriterSettings) $xml.WriteTo($XmlWriter) $XmlWriter.Flush() $StringWriter.Flush() $XmlWriter.Close() $Result = $StringWriter.ToString() $StringWriter.Dispose() $Result } } <# .SYNOPSIS Retrieves the content of a given control element. .DESCRIPTION The Get-ControlContent function retrieves the content of a given control element based on its properties, such as Content, Header, Text, or SelectedItem. .PARAMETER Element The control element from which to retrieve the content. .EXAMPLE Get-ControlContent -Element $button Retrieves the content of the $button element. .NOTES Author: Brooks Vaughn Date: 2025-02-19 18:16:11 #> Function Get-ControlContent { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] $Element ) Switch ($Element) { { $_.Content } { Return $_.Content } { $_.Header } { Return $_.Header } { $_.Text } { Return $_.Text } { $_.SelectedItem } { Return $_.SelectedItem } Default { Return $_.Name } } } <# .SYNOPSIS Opens a Windows OpenFileDialog to select a file. .DESCRIPTION This function opens a Windows OpenFileDialog to allow the user to select a file. It supports options for initial directory, file filter, title dialog, and multi-select. .PARAMETER InitialDirectory The initial directory to open the file dialog. .PARAMETER FileFilter The file filter to apply in the dialog. .PARAMETER TitleDialog The title of the file dialog. .PARAMETER AllowMultiSelect Specifies if multiple files can be selected. .PARAMETER SaveAs Specifies if the dialog should be a SaveFileDialog. .EXAMPLE $fileName = Get-FileFromDialog -fileFilter 'CSV file (*.csv)|*.csv' -titleDialog "Select A CSV File:" .NOTES Author: Brooks Vaughn Date: "22 February 2025" #> Function Get-FileFromDialog { [CmdletBinding()] Param ( [Parameter(Position=0)] [string]$InitialDirectory = './', [Parameter(Position=0)] [string]$InitialFileName, [Parameter(Position=1)] [string]$FileFilter = 'All files (*.*)| *.*', [Parameter(Position=2)] [string]$TitleDialog = '', [Parameter(Position=3)] [switch]$AllowMultiSelect = $false, [switch]$SaveAs ) [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null If (-Not $SaveAs) { $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog } Else { $OpenFileDialog = New-Object System.Windows.Forms.SaveFileDialog # $OpenFileDialog.ValidateNames = $false $OpenFileDialog.OverwritePrompt = $false } $OpenFileDialog.InitialDirectory = $InitialDirectory $OpenFileDialog.FileName = $InitialFileName $OpenFileDialog.Filter = $FileFilter $OpenFileDialog.Title = $TitleDialog $OpenFileDialog.ShowHelp = If ($Host.Name -eq 'ConsoleHost') { $true } Else { $false } If ($AllowMultiSelect) { $OpenFileDialog.MultiSelect = $true } $OpenFileDialog.ShowDialog() | Out-Null If ($AllowMultiSelect) { Return $OpenFileDialog.Filenames } Else { Return $OpenFileDialog.Filename } } <# .SYNOPSIS Displays WPF_* form variables which are XAML elements whose x:Name begins with an _ underscore character. .DESCRIPTION This function displays a list of WPF_* variables of XAML Elements which can accessed and referenced directly, .EXAMPLE Get-FormVariable ----------------------------------------------------------------- Found the following intractable elements from our form ----------------------------------------------------------------- Name Value ---- ----- WPF_dataGrid System.Windows.Controls.DataGrid Items.Count:0 WPF_saveButton System.Windows.Controls.Button: Save ----------------------------------------------------------------- .NOTES Author: Brooks Vaughn Date: Wednesday, 19 February 2025 19:26 #> Function Get-FormVariable { [CmdletBinding()] param () if ($null -eq $ReadmeDisplay -or $ReadmeDisplay -eq $false) { "If you need to reference this display again, run Get-FormVariables" } ("`n$("-" * 65)`nGet-FormVariable() Found the following intractable elements from the XAML form`n$("-" * 65)") ((Get-Variable WPF*) | Select-Object -Property Name, Value | Out-String).Trim() ("$("-" * 65)`n") } <# .SYNOPSIS Returns a PSObject containing details about an object's properties. .DESCRIPTION The Get-ObjectPropertyDetail function returns a PSObject that lists the properties of the input object along with their names, values, and types. .PARAMETER InputObject The object whose properties are to be detailed. .EXAMPLE $details = Get-ObjectPropertyDetail -InputObject $form1 $details | Format-Table -AutoSize $details | Format-Table -AutoSize -Force -Wrap -Property Property, Value, Type Retrieves the properties of the object $form1 and displays them in a formatted table. .NOTES Author: Brooks Vaughn Date: 2025-02-18 15:41:05 #> Function Get-ObjectPropertyDetail { [CmdletBinding()] Param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] [Object]$InputObject ) Begin { $ReturnObject = [System.Collections.Generic.List[PSCustomObject]]::new() } Process { ForEach ($property in $InputObject.PSObject.Properties) { $typeName = [Microsoft.PowerShell.ToStringCodeMethods]::Type([type]$property.TypeNameOfValue) $value = $property.Value If ($typeName -like '*IScriptExtent*') { $file = if ($null -eq $value.File) { "" } else { Split-Path -Leaf $value.File } $value = "{0} ({1},{2})-({3},{4})" -f $file, $value.StartLineNumber, $value.StartColumnNumber, $value.EndLineNumber, $value.EndColumnNumber } [void]$ReturnObject.Add([PSCustomObject]@{ Property = $property.Name Value = $value Type = $typeName }) } } End { $ReturnObject } } <# .SYNOPSIS Creates a unique filename by adding a sequence number or timestamp to ensure the filename is unique. .DESCRIPTION This function creates a unique filename based on an input filename used as a template. It adds a date timestamp and/or a sequence number (.01, .02,...,.99) if the new file name already exists. .PARAMETER Name The existing filename to make Unique .PARAMETER NoDate Specifies not to add a date to the filename. .PARAMETER AddTime Specifies to add a time to the filename. .PARAMETER AddSeq Specifies to add a sequence number to the filename. .EXAMPLE Get-UniqueFileName -Name "FileNameTemplate.ext" .EXAMPLE Get-UniqueFileName -Name "C:\Temp\Prod" .EXAMPLE Get-UniqueFileName -Name "C:\Temp\Prod" -AddSeq .EXAMPLE Get-UniqueFileName -Name "C:\Temp\Prod" -NoDate .EXAMPLE Get-UniqueFileName -Name "C:\Temp\Prod" -NoDate -AddSeq Get-UniqueFileName -Name C:\Git\GuiMyPS\src\public\Get-UniqueFileName.ps1 -NoDate -AddSeq Get-UniqueFileName -Name FileNameTemplate_2025-0223.ext -NoDate -AddSeq .NOTES Author: Brooks Vaughn Date: "22 February 2012" #> Function Get-UniqueFileName { [CmdletBinding()] Param ( [Parameter(HelpMessage="Existing FileName to Copy", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [ValidateNotNullOrEmpty()] [string]$Name, [switch]$NoDate, [switch]$AddTime, [switch]$AddSeq ) Begin { $NewFileName = [String]::Empty # Save File Ext and Get Root of File $PathToFile = [System.IO.Path]::GetDirectoryName($Name) $Ext = [System.IO.Path]::GetExtension($Name) $FileName = [System.IO.Path]::GetFileNameWithoutExtension($Name) # Strip Off Date / Time $FileName = ($FileName -replace "_\d{4}-\d{4}\D{1}?\d{4,6}|_\d{4}-\d{4}", "") # Strip off any Sequence such as .000 to .999 extension If ([System.IO.Path]::GetExtension($FileName) -match "\.\d{3}") { $FileName = [System.IO.Path]::GetFileNameWithoutExtension($FileName) } } Process { Try { If (-not $NoDate) { # Add Date Stamp $FileName += "_$(Get-Date -Format yyyy-MMdd)" # Add Time Stamp If ($AddTime) { # Add Time Stamp to FileName $FileName += "-$(Get-Date -Format HHmm)" } } if ([String]::IsNullOrEmpty($newFileName)) { $newFileName = [System.IO.Path]::Combine($pathToFile, "$FileName$($ext)") } # Verify that New FileName does not already exist and Add Sequence # when it does $i = 0 While ((Test-Path -Path $NewFileName) -or $AddSeq) { $AddSeq = $false $i++ $Seq = "{0:D3}" -f ([int]$i) $NewFileName = [System.IO.Path]::Combine($PathToFile, "$FileName.$Seq$($Ext)") } $Host.UI.WriteDebugLine("Get-UniqueFileName() New FileName: ($NewFileNameItem)") } Catch { Write-Error $_ } } End { $NewFileName } } <# .SYNOPSIS Verifies that the required modules are installed and imported. .DESCRIPTION This function checks if a specified module is installed. If not, it installs the module from the PSGallery repository and imports it. If the module is already installed, it simply imports the module. .PARAMETER ModuleName The name of the module to verify, install, and import. .EXAMPLE Initialize-Module -ModuleName "SqlQueryClass" .EXAMPLE Initialize-Module -ModuleName "GuiMyPS" .EXAMPLE Initialize-Module -ModuleName "ImportExcel" .NOTES Author: Brooks Vaughn Date: "22 February 2025" #> Function Initialize-Module { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string]$ModuleName ) # Check if the module is already installed $module = Get-Module -ListAvailable -Name $ModuleName If ($null -eq $module) { # Module is not installed, install it Write-Host "Module '$ModuleName' is not installed. Installing..." Install-Module -Name $ModuleName -Repository PSGallery -Scope CurrentUser # Import the newly installed module Write-Host "Importing module '$ModuleName'..." Import-Module -Name $ModuleName } Else { # Module is already installed, import it Write-Host "Module '$ModuleName' is already installed. Importing..." Import-Module -Name $ModuleName } # Verify the module is imported If (Get-Module -Name $ModuleName) { Write-Host "Module '$ModuleName' has been successfully imported." } Else { Write-Host "Failed to import module '$ModuleName'." } } <# .SYNOPSIS New-Popup -- Display a Popup Message .DESCRIPTION This command uses the Wscript.Shell PopUp method to display a graphical message box. You can customize its appearance of icons and buttons. By default the user must click a button to dismiss but you can set a timeout value in seconds to automatically dismiss the popup. The command will write the return value of the clicked button to the pipeline: OK = 1 Cancel = 2 Abort = 3 Retry = 4 Ignore = 5 Yes = 6 No = 7 If no button is clicked, the return value is -1. .OUTPUTS Null = -1 OK = 1 Cancel = 2 Abort = 3 Retry = 4 Ignore = 5 Yes = 6 No = 7 .EXAMPLE PS C:\> New-Popup -Message "The update script has completed" -Title "Finished" -Time 5 This will display a popup message using the default OK button and default Information icon. The popup will automatically dismiss after 5 seconds. .NOTES Original Source: http://powershell.org/wp/2013/04/29/powershell-popup/ Original Source: The Lonely Administrator's https://jdhitsolutions.com/blog/powershell/2976/powershell-popup/ Last Updated: April 8, 2013 Version : 1.0 Similar to: Bill Riedy's https://www.powershellgallery.com/packages/PoshFunctions/2.2.1.6/Content/Functions%5CNew-Popup.ps1 #> #------------------------------------------------------------------ # Display a Popup Message #------------------------------------------------------------------ Function New-Popup { [CmdletBinding(SupportsShouldProcess = $true)] Param ( [Parameter(Position=0, Mandatory=$true, HelpMessage="Enter a message for the popup")] [ValidateNotNullOrEmpty()] [string]$Message, [Parameter(Position=1, Mandatory=$true, HelpMessage="Enter a title for the popup")] [ValidateNotNullOrEmpty()] [string]$Title, [Parameter(Position=2, HelpMessage="How many seconds to display? Use 0 to require a button click.")] [ValidateScript({$_ -ge 0})] [int]$Time = 0, [Parameter(Position=3, HelpMessage="Enter a button group")] [ValidateNotNullOrEmpty()] [ValidateSet("OK", "OKCancel", "AbortRetryIgnore", "YesNo", "YesNoCancel", "RetryCancel")] [string]$Buttons = "OK", [Parameter(Position=4, HelpMessage="Enter an icon set")] [ValidateNotNullOrEmpty()] [ValidateSet("Stop", "Question", "Exclamation", "Information")] [string]$Icon = "Information" ) Process { if ($PSCmdlet.ShouldProcess("Display Popup Message")) { # Convert buttons to their integer equivalents Switch ($Buttons) { "OK" {$ButtonValue = 0} "OKCancel" {$ButtonValue = 1} "AbortRetryIgnore" {$ButtonValue = 2} "YesNo" {$ButtonValue = 4} "YesNoCancel" {$ButtonValue = 3} "RetryCancel" {$ButtonValue = 5} } } # Set an integer value for icon type Switch ($Icon) { "Stop" {$IconValue = 16} "Question" {$IconValue = 32} "Exclamation" {$IconValue = 48} "Information" {$IconValue = 64} } # Create the COM Object Try { $Wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop # Button and icon type values are added together to create an integer value $Wshell.Popup($Message, $Time, $Title, $ButtonValue + $IconValue) } Catch { Write-Warning "Failed to create Wscript.Shell COM object" Write-Warning $_.Exception.Message } } } <# .SYNOPSIS Helper function that processes and loads XAML (as string, filename, or as [XML] object) into a WPF Form Object. .DESCRIPTION The New-XamlWindow function processes and loads XAML (as string, filename, or as [XML] object) into a WPF Form Object. .PARAMETER xaml The XAML content to process and load. .PARAMETER NoXRemoval A switch to prevent the removal of x:Name attributes. .EXAMPLE # Test for XAML String try { $form1 = New-XamlWindow -xaml $inputXML Add-ClickToEveryButton -Element $form1 -ClickHandler $handler_button_Click $form1.ShowDialog() } catch { Write-Warning ($_ | Format-List | Out-String) } .EXAMPLE # Test for File Path to XAML file with two approaches for Adding Click Events # Note: Code for $handler_MenuItem_Click can be generated using Build-HandlerCode() try { $form = New-XamlWindow -xaml 'C:\Git\SqlQueryEditor\src\resources\SqlQueryEditor.xaml' # Add-ClickToEveryMenuItem -MenuObj $WPF_menuMasterDataGrid -Handler $handler_MenuItem_Click # Add-ClickToEveryMenuItem -MenuObj $WPF_menuDetailDataGrid -Handler $handler_MenuItem_Click $elements = @() $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' $elements.ForEach({$_.Element.Add_Click($handler_MenuItem_Click)}) $form.ShowDialog() } catch { Write-Warning ($_ | Format-List | Out-String) } .EXAMPLE # Test for [System.Xml.XmlDocument] try { $xaml = [xml]$inputXML $form2 = New-XamlWindow -xaml $xaml Add-ClickToEveryButton -Element $form2 -ClickHandler $handler_button_Click $form2.ShowDialog() } catch { Write-Warning ($_ | Format-List | Out-String) } .NOTES Author: Brooks Vaughn Date: 2025-02-19 18:28:36 #> Function New-XamlWindow { [CmdletBinding(SupportsShouldProcess = $true)] Param ( [Parameter(Mandatory = $true)] [Alias("InputObject")] [System.Object]$xaml, [Parameter(Mandatory = $false)] [switch]$NoXRemoval ) # Load the necessary assemblies Add-Type -AssemblyName PresentationFramework <# XAML Elements might use either x:Name="" or Name="" The x: refers to the namespace xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" which is not automatically defined in PowerShell. To use XPath queries during preprocessing of the XAML Text, you have to create a NamespaceManager and add the missing namespaces. For x:Name, the XPath query would be $xaml.SelectNodes("//*[@x:Name]", $nsManager). For Name, the XPath query would be $xaml.SelectNodes("//*[@Name]", $nsManager). By removing x:Name from the XAML string before converting to [System.Xml.XmlDocument], there is no need for the NamespaceManager and XPath needs only $xaml.SelectNodes("//*[@Name]"). #> Function Remove-XName { [CmdletBinding(SupportsShouldProcess = $true)] Param ( [Parameter(Mandatory = $true)] [string]$xamlString, [Parameter(Mandatory = $false)] [switch]$NoXRemoval ) If ($NoXRemoval) { $xamlString } Else { If ($PSCmdlet.ShouldProcess("XAML String", "Remove x:Name attributes")) { ((($xamlString -replace 'mc:Ignorable="d"', '') -replace "x:Na", 'Na') -replace '^<Win.*', '<Window') } Else { $xamlString } } } # Define return and working variables $uiForm = $null $xamlReader = $null Try { # Process the $xaml input object into an XmlDocument and create reader If ($xaml -is [System.Xml.XmlDocument]) { $newXaml = $xaml } ElseIf ($xaml -is [System.String]) { If (Test-Path -Path $xaml -PathType Leaf -ErrorAction SilentlyContinue) { $xamlString = Remove-XName -NoXRemoval:$NoXRemoval -xamlString (Get-Content -Path $xaml -Raw) If ($xamlString -match '<(Window|Grid|StackPanel|Button|DataGrid|TextBox)') { $newXaml = [xml]$xamlString } Else { Throw "-xaml parameter as file path was valid but its content is not XAML" } } ElseIf ($xaml -match '<(Window|Grid|StackPanel|Button|DataGrid|TextBox)') { $xamlString = Remove-XName -NoXRemoval:$NoXRemoval -xamlString $xaml $newXaml = [xml]$xamlString } Else { Throw "-xaml parameter as XAML String has no valid XAML content" } } Else { Throw "-xaml is not a valid [System.Xml.XmlDocument], XAML string, or filepath to a XAML file" } # Perform XML data cleanup and create PowerShell $WPF_* Variables for XAML names that begin with "_" # Remove unsupported namespaces created by XAML editors like Blend / Visual Studio $newXaml.Window.RemoveAttribute('x:Class') $newXaml.Window.RemoveAttribute('d') $newXaml.Window.RemoveAttribute('mc:Ignorable') $problemObjects = $newXaml.SelectNodes("//*[@Name]").Where({ $_.Click -or $_.TextChanged -or $_.SelectionChanged -or $_.Checked -or $_.Unchecked -or $_.ValueChanged}) | Select-Object -Property Name, LocalName, Click, TextChanged, SelectionChanged, Checked, Unchecked, ValueChanged $errorHeading = "XAML String has unsupported events defined and cannot be converted by PowerShell WPF" + [System.Environment]::NewLine $errorText = ForEach ($obj in $problemObjects) { Switch ($obj) { { $_.Click } { "$($_.LocalName) named ($($_.Name)) has Click event and needs to be removed"; break } { $_.TextChanged } { "$($_.LocalName) named ($($_.Name)) has TextChanged event and needs to be removed"; break } { $_.SelectionChanged } { "$($_.LocalName) named ($($_.Name)) has SelectionChanged event and needs to be removed"; break } { $_.Checked } { "$($_.LocalName) named ($($_.Name)) has Checked event and needs to be removed"; break } { $_.Unchecked } { "$($_.LocalName) named ($($_.Name)) has Unchecked event and needs to be removed"; break } { $_.ValueChanged } { "$($_.LocalName) named ($($_.Name)) has ValueChanged event and needs to be removed"; break } Default { "Skipping: $($_.LocalName) named ($($_.Name))" } } } If ($problemObjects.Count -gt 0) { Write-Host ($errorHeading) -ForegroundColor Red Write-Host ($errorText | Out-String) -ForegroundColor Magenta Write-Host "Please remove the event definitions from the XAML and try again" -ForegroundColor Red Exit } # Create and Load the XAML Reader to create the WPF Form $xamlReader = (New-Object System.Xml.XmlNodeReader $newXaml) $uiForm = [System.Windows.Markup.XamlReader]::Load($xamlReader) # Create a PowerShell Variable $WPF_ for each Form Object defined in the XAML where the # element name begin with "_" as in Name="_MyObj" or x:Name="_MyObj" # For the most part, this allows the Code-Behind to directly access the element without needing to find it first $FormObjects = $newXaml.SelectNodes("//*[@Name]").Where({ $_.Name -like "_*" }).ForEach({ $element = $uiForm.FindName($_.Name) If ($element) { Write-Output ("WPF$($_.Name)") Set-Variable -Name "WPF$($_.Name)" -Value $element -Force -Scope Global -Visibility Public } Else { Write-Warning "Unable to locate $($_.Name) element name" } }) Write-Verbose "`nList of `$WPF_ named Variables of XAML Elements`n$($FormObjects | Out-String)`n" $uiForm } Catch { Write-Error ($_ | Format-List | Out-String) } } <# .SYNOPSIS Saves the content of a DataGrid to various formats such as Clipboard, CSV, or Excel. .DESCRIPTION This function converts the content of a DataGrid and saves it to the specified format. It supports saving to Clipboard, CSV files, and Excel files. .PARAMETER InputObject The object to convert and save. This can be a DataGrid or DataRowView. .PARAMETER Path The path and filename of the SaveAs file. .PARAMETER SaveAs The target output format. This can be Clipboard, CSV, or Excel. .EXAMPLE Save-DataGridContent -InputObject $WPF_dataGridSqlQuery -SaveAs CVS -Verbose Save-DataGridContent -InputObject $WPF_dataGridSqlQuery -SaveAS CSV -Path C:\Temp\dataGridSqlQuery.csv $WPF_dataGridSqlQuery | Save-DataGridContent -SaveAs Excel -Verbose Save-DataGridContent -InputObject $WPF_dataGridSqlQuery -SaveAs Clipboard -Verbose .NOTES Author: Brooks Vaughn Date: "22 February 2025" #> Function Save-DataGridContent { [CmdletBinding()] Param ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, HelpMessage='Object to convert and save')] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSObject]$InputObject, [Parameter(Mandatory=$false, Position=1, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, HelpMessage='Path and Filename of SaveAs File')] [ValidateScript({ $Parent = Split-Path $_ -Parent -ErrorAction SilentlyContinue If (-Not (Test-Path -Path $Parent -PathType Container -ErrorAction SilentlyContinue)) { Throw "Specify a valid path. Parent '$Parent' does not exist: $_" } $true })] [string]$Path, [Parameter(Position=2, Mandatory=$false, HelpMessage='Target output format')] [ValidateSet('Clipboard', 'CSV', 'Excel')] [string]$SaveAs = 'Clipboard' ) Begin { Add-Type -AssemblyName System.Windows.Forms $RowCount = 0 # $PipelineInput = -Not $PSBoundParameters.ContainsKey('InputObject') # $Index = 1 $OutObject = @() } Process { If ($SaveAs -eq 'clipboard' -and $InputObject -is 'System.Windows.Controls.DataGrid') { ForEach ($Row in $InputObject.Items) { If ($Row.ToString() -eq '{NewItemPlaceholder}') { Continue } $RowCount++ If ($RowCount -ge $InputObject.Items.Count) { $Row Break } $OutObject += "Record # {0}{1}" -f $RowCount, [System.Environment]::NewLine ForEach ($Column in $InputObject.Columns.Header) { $OutObject += "{0} : {1}{2}" -f $Column, ($Row."$Column"), [System.Environment]::NewLine } $OutObject += [System.Environment]::NewLine } $OutObject += "Total Record Count: {0}{1}" -f $RowCount, [System.Environment]::NewLine } ElseIf ($SaveAs -eq 'clipboard' -and $InputObject -is 'System.Data.DataRowView') { ForEach ($Row in $InputObject) { $RowCount++ $OutObject += "Record # {0}{1}" -f $RowCount, [System.Environment]::NewLine ForEach ($Column in (($Row."Row").PSObject.Properties.Name | Where-Object {$_ -notin @('RowError','RowState','Table','ItemArray','HasErrors')})) { $OutObject += "{0} : {1}{2}" -f $Column, ($Row."$Column"), [System.Environment]::NewLine } $OutObject += [System.Environment]::NewLine } $OutObject += "Total Record Count: {0}{1}" -f $RowCount, [System.Environment]::NewLine } } End { Switch ($SaveAs) { 'clipboard' { [System.Windows.Forms.Clipboard]::SetText($OutObject) Write-Host ("Record Copied to {0}" -f $SaveAs) } 'CSV' { $SaveFile = Get-FileFromDialog -InitialDirectory $Parent -InitialFileName $Path -TitleDialog "Specify SaveAs $SaveAs path and filename" -FileFilter 'CSV files (*.csv)|*.csv|CSV files (*.txt)|*.txt|All files (*.*)|*.*' -SaveAs If (-Not [string]::IsNullOrEmpty($SaveFile)) { $SaveFile = Get-UniqueFileName -Name $SaveFile $InputObject.Items.Where({$_.ToString() -ne '{NewItemPlaceholder}'}) | Export-Csv -Path $SaveFile -Force -NoTypeInformation Write-Host ("{0} Saved to: {1}" -f $SaveAs, $SaveFile) } Else { Write-Host ("SaveAs {0} aborted by user" -f $SaveAs) } } 'Excel' { $SaveFile = Get-FileFromDialog -InitialDirectory $Path -TitleDialog "Specify SaveAs $SaveAs path and filename" -FileFilter 'Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*' -SaveAs If (-Not [string]::IsNullOrEmpty($SaveFile)) { $XLFile = Get-UniqueFileName -Name $SaveFile Export-XLSX -InputObject $InputObject.Items -Path $XLFile -Table -Autofit -WorksheetName $rsObject.QueryDB Write-Host ("{0} Saved to: {1}" -f $SaveAs, $SaveFile) } Else { Write-Host ("SaveAs {0} aborted by user" -f $SaveAs) } } Default {} } } } <# .SYNOPSIS Saves a dataset to an Excel file. .DESCRIPTION This function converts a dataset to an Excel file and saves it to the specified path. .PARAMETER InputObject The dataset to convert and save. .PARAMETER Path The path and filename of the SaveAs file. .EXAMPLE .NOTES Author: Brooks Vaughn Date: "22 February 2025" #> Function Save-DatasetToExcel { [CmdletBinding()] Param ( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, HelpMessage='Object to convert and save')] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSObject]$InputObject, [Parameter(Mandatory=$false, Position=1, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, HelpMessage='Path and Filename of SaveAs File')] [ValidateScript({ $Parent = Split-Path $_ -Parent -ErrorAction SilentlyContinue If (-not (Test-Path -Path $Parent -PathType Container -ErrorAction SilentlyContinue)) { Throw "Specify a valid path. Parent '$Parent' does not exist: $_" } $true })] [string]$Path ) Begin { Initialize-Module -ModuleName "ImportExcel" # $RowCount = 0 # $PipelineInput = -not $PSBoundParameters.ContainsKey('InputObject') # $Index = 1 # $OutObject = @() If (-not [string]::IsNullOrEmpty($Path)) { # Create unique TimeStamped File pathname for saving output to $XLFile = Get-UniqueFileName -Name "$Parent\$Path" Write-Host ("Exporting DataSet To: {0}" -f $XLFile) } Else { Write-Host ("Error: -Path is missing or empty") $XLFile = Get-FileFromDialog -initialDirectory $Path -titleDialog "Specify SaveAs $Parent path and filename" -fileFilter 'Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*' -SaveAs } # ------------------------------------------------------------------ # Begin Excel Creation # ------------------------------------------------------------------ Write-Host ("Exporting Query Results to Excel File ($XLFile)") $xlApp = New-Object OfficeOpenXml.ExcelPackage $XLFile [void]$xlApp.Workbook.Worksheets.Add($WorkSheetName) $wbSource = $xlApp.Workbook # Update workBook properties $wbSource.Properties.Author = $env:USERNAME $wbSource.Properties.Title = "Query Results Export" $wbSource.Properties.Subject = "Query Results Export" $wbSource.Properties.Company = "" # ----------------------------------------------------- # Create Queries Worksheet and Populate # ----------------------------------------------------- Add-WorkSheet -Workbook $wbSource -Name 'Query' -Headers @{ 'Query Number' = 1; 'SQL Query' = 2 } -HeaderBorderStyle ([OfficeOpenXml.Style.ExcelBorderStyle]::Thin) $xlApp.Save() Close-ExcelPackage -ExcelPackage $xlApp $xlApp.Dispose() $xlApp = $null } Process { # Add each Result Set ($InputObject.Value) as a new worksheet named as ($InputObject.Name) Export-XLSX -InputObject $InputObject.Value -Path $XLFile -Table -Autofit -WorksheetName $InputObject.Name } End { Write-Host ("Export Completed") } } <# .SYNOPSIS Sets a message in a WPF String control using DispatcherTimer to Set Message Box via WPF String control. .DESCRIPTION This function sets a message in a WPF String control and allows for various options such as clearing the message, adding new lines before or after, and avoiding new lines. Requires Defining an XAML: Element named Messages which gets updated by a DispatchTimer <Window.Resources> <system:String x:Key="Messages">Messages:</system:String> <Window.Resources> <ScrollViewer x:Name="_svMessages" Padding="0,0,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <TextBox x:Name="_tbxMessages" Margin="5,2,5,5" Padding="0,0,0,0" Text="{DynamicResource ResourceKey=Messages}" ScrollViewer.CanContentScroll="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Top" TextWrapping="WrapWithOverflow" AcceptsReturn="True" Cursor="Arrow" /> </ScrollViewer> .PARAMETER Message The message to set in the WPF String control. .PARAMETER Clear Clears the current message. .PARAMETER NoNewline Specifies not to add a newline after the message. .PARAMETER NewlineBefore Adds a newline before the message. .PARAMETER NewlineAfter Adds a newline after the message. .EXAMPLE Set-Message -Resources $syncHash.Form.Resources -Message 'This is a test message' Set-Message -Resources $syncHash.Form.Resources -Message 'This is a test message' -Clear .NOTES Author: Brooks Vaughn Date: "22 February 2025" #> Function Set-Message { [CmdletBinding(SupportsShouldProcess = $true)] Param ( [Object]$Resources = $SyncHash.Form.Resources, [string]$Message = [string]::Empty, [switch]$Clear, [switch]$NoNewline, [switch]$NewlineBefore, [switch]$NewlineAfter ) If ($PSCmdlet.ShouldProcess("Setting message")) { If ($Clear) { $Message = [string]::Empty $Resources["Messages"] = [string]::Empty } } If ($NewlineBefore) { $Resources["Messages"] += [System.Environment]::NewLine } If (-not [string]::IsNullOrEmpty($Message)) { If ($NoNewline) { $Resources["Messages"] += $Message } Else { $Resources["Messages"] += $Message + [System.Environment]::NewLine } Write-Host $Message -NoNewline:$NoNewline } If ($NewlineAfter) { $Resources["Messages"] += [System.Environment]::NewLine } } |