Public/ConfigCommands/Update-RpConfigCommand.ps1
function Update-RpConfigCommand { <# .SYNOPSIS Modifies configuration parameters of a specified command in a PowerShell module using a configuration JSON file or an interactive WPF-based GUI. .DESCRIPTION The `Update-RpConfigCommand` function is used to load and modify parameters for a specified command within a PowerShell module. The function retrieves configuration information from a JSON file and supports updating parameters either by launching a GUI (if `-ShowDialog` is specified) or by directly passing a hashtable of parameter values through the `-Parameters` parameter. The updated configuration is saved back to the JSON file, preserving all previous configurations. By default, the function opens a GUI for parameter editing, making it user-friendly for interactive scenarios. Advanced users and scripts can also bypass the GUI and pass values directly through `-Parameters`. .COMPONENT ConfigCommands .PARAMETER ModuleName The name of the module containing the command to be modified. This parameter is mandatory and required for identifying the correct module in the configuration. .PARAMETER CommandName The name of the specific command to be modified within the module. This parameter is mandatory and helps locate the command configuration within the JSON file. .PARAMETER Id A unique integer ID identifying the command version in the configuration file. This ensures that specific versions of the command can be targeted and modified. .PARAMETER ConfigFilePath The path to the configuration JSON file where command configurations are stored. This file is required and will be updated with any modified parameter values. .PARAMETER Parameters (Optional) A hashtable containing parameter names as keys and their desired values as values. Use this parameter to bypass the GUI and directly update the command configuration with the specified values. .PARAMETER Description (Optional) A string containing the new description for the command. Use this parameter to update the command description directly. .PARAMETER ShowDialog (Optional) Displays a WPF GUI dialog for interactive parameter editing. If this switch is specified, any `Parameters` passed directly will be ignored, and the GUI will allow users to make adjustments. .EXAMPLE Update-RpConfigCommand -ModuleName 'MilestonePSTools' -CommandName 'Get-VmsCameraReport' -Id 18 -ConfigFilePath $(Get-Rpconfigpath) -ShowDialog This example loads the configuration for the 'Get-VmsCameraReport' command in the 'MilestonePSTools' module and opens a GUI dialog to allow users to interactively edit parameter values. Once edited, the configuration is saved back to the JSON file. .EXAMPLE Update-RpConfigCommand -ModuleName 'MilestonePSTools' -CommandName 'Get-VmsCameraReport' -Id 18 -ConfigFilePath $(Get-Rpconfigpath) -Parameters @{ "Verbose" = $true; "Debug" = $false } This example directly sets the 'Verbose' and 'Debug' parameters without opening the GUI. A hashtable is provided via the `-Parameters` parameter, which updates the configuration in the JSON file with these values. .EXAMPLE Update-RpConfigCommand -ModuleName 'MilestonePSTools' -CommandName 'Get-VmsCameraReport' -Id 18 -ConfigFilePath $(Get-Rpconfigpath) -Description "New description for the command" This example updates the description of the 'Get-VmsCameraReport' command in the 'MilestonePSTools' module without opening the GUI. .NOTES The configuration file must be in JSON format and already include a structure for ConfigCommands and the specified module. Any missing structure will cause an error. Note: The order of parameters in the GUI may not match the JSON file order due to how PowerShell handles JSON deserialization. To ensure a specific order, consider using an OrderedDictionary or manually managing parameter order. Ensure that the PresentationFramework, PresentationCore, and WindowsBase assemblies are available for WPF support to allow the GUI dialog to open. .LINK https://www.remotepro.dev/en-US/Update-RpConfigCommand #> [CmdletBinding(DefaultParameterSetName = 'ShowDialog')] param ( [Parameter(ParameterSetName = 'ShowDialog', ValueFromPipelineByPropertyName = $true)] [Parameter(ParameterSetName = 'ConfigurationItems', ValueFromPipelineByPropertyName = $true)] [string]$ModuleName, [Parameter(ParameterSetName = 'ShowDialog', ValueFromPipelineByPropertyName = $true)] [Parameter(ParameterSetName = 'ConfigurationItems', ValueFromPipelineByPropertyName = $true)] [string]$CommandName, [Parameter(ParameterSetName = 'ShowDialog', ValueFromPipelineByPropertyName = $true)] [Parameter(ParameterSetName = 'ConfigurationItems', ValueFromPipelineByPropertyName = $true)] [string]$Id, [Parameter(ParameterSetName = 'ConfigurationItems', ValueFromPipelineByPropertyName = $true)] [Parameter(ParameterSetName = 'NoDialog', ValueFromPipelineByPropertyName = $true)] [hashtable]$Parameters, [Parameter(ParameterSetName = 'ConfigurationItems', ValueFromPipelineByPropertyName = $true)] [Parameter(ParameterSetName = 'NoDialog', ValueFromPipelineByPropertyName = $true)] [string]$Description, [Parameter(ParameterSetName = 'ShowDialog')] [Parameter(ParameterSetName = 'ConfigurationItems')] [string]$ConfigFilePath, [Parameter(ParameterSetName = 'ShowDialog')] [switch]$ShowDialog ) begin { try { Add-Type -AssemblyName PresentationFramework } catch { Write-Error "Failed to load PresentationFramework assembly: $_" return if (-not $PSBoundParameters.ContainsKey('ConfigFilePath') -or [string]::IsNullOrWhiteSpace($ConfigFilePath)) { $ConfigFilePath = Get-RpConfigPath } } } process { # Use appdata path if there is not a filepath value. if (-not ($ConfigFilePath)){ $ConfigFilePath = Get-RpConfigPath } # Load configuration JSON $configContent = Get-Content -Path $ConfigFilePath -Raw $config = $configContent | ConvertFrom-Json if (-not $config.ConfigCommands.PSObject.Properties[$ModuleName]) { Write-Error "Module '$ModuleName' not found in the configuration." return } $commandDetails = $config.ConfigCommands.$ModuleName | Where-Object { $_.CommandName -eq $CommandName -and $_.Id -eq $Id } if (-not $commandDetails) { Write-Error "Command '$CommandName' with ID '$Id' not found in module '$ModuleName'." return } function Convert-StringToHashtable { param ([string]$parameterstring) $parameterstring = $parameterstring.TrimStart('@{').TrimEnd('}') $parameterstring = $parameterstring -replace '; ', "`n" return $parameterstring | ConvertFrom-StringData } function Convert-HashtableToString { param ([hashtable]$hash) $orderedKeys = @('Type', 'Mandatory', 'Value') $string = '@{' foreach ($key in $orderedKeys) { if ($hash.ContainsKey($key)) { $string += "$key=$($hash[$key]); " } } $string.TrimEnd('; ') + '}' } foreach ($paramName in $commandDetails.Parameters.PSObject.Properties.Name) { $paramDetails = $commandDetails.Parameters.$paramName if ($paramDetails -is [string]) { $commandDetails.Parameters.$paramName = Convert-StringToHashtable -parameterString $paramDetails } } # Load values from config if no Parameters are provided if (-not $parameters) { $parameters = @{} foreach ($paramName in $commandDetails.Parameters.PSObject.Properties.Name) { $parameters[$paramName] = $commandDetails.Parameters.$paramName.Value } } # GUI Dialog for Parameter Editing if ($ShowDialog) { Write-Verbose "Opening WPF GUI to edit parameter values for '$CommandName' (ID: $Id)..." $XAML = @" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:mdControls="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf" Title="Edit Parameters" MinHeight="600" MinWidth="400" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight" Background="{DynamicResource MaterialDesignPaper}"> <Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <materialDesign:BundledTheme x:Key="AppTheme" BaseTheme="Light" PrimaryColor="Grey" SecondaryColor="Lime" /> <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <StackPanel> <Button Name="HelpButton" Width="100" Margin="10" Content="Get Help" Style="{StaticResource MaterialDesignFlatButton}" HorizontalAlignment="Right" /> <ScrollViewer VerticalScrollBarVisibility="Auto" Height="500"> <StackPanel Name="ParameterStack"> </StackPanel> </ScrollViewer> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="10"> <Button Name="SubmitButton" Width="75" Margin="10" Content="Submit" Style="{StaticResource MaterialDesignFlatButton}" /> <Button Name="CancelButton" Width="75" Margin="10" Content="Cancel" Style="{StaticResource MaterialDesignFlatButton}" /> </StackPanel> </StackPanel> </Window> "@ $window = [Windows.Markup.XamlReader]::Parse($xaml) # Set the window icon if ($null -ne $window) { Set-RpWindowIcon -window $window } else { Write-Warning "WPF window failed to load. Cannot set icon." } $parameterstack = $window.FindName("ParameterStack") $submitButton = $window.FindName("SubmitButton") $cancelButton = $window.FindName("CancelButton") $helpButton = $window.FindName("HelpButton") $textBoxes = @{} foreach ($paramName in $parameters.Keys) { $paramValue = $parameters[$paramName] # Label and textbox setup for each parameter $label = New-Object Windows.Controls.TextBlock $label.Text = $paramName $label.Margin = "5,5,5,5" $parameterstack.Children.Add($label) | Out-Null $textBox = New-Object Windows.Controls.TextBox $textBox.Text = $paramValue # Set textbox text to existing parameter value $textBox.Margin = "5,5,5,5" $parameterstack.Children.Add($textBox) | Out-Null # Store the textbox in a hashtable for later retrieval $textBoxes[$paramName] = $textBox } # Add description box $descriptionLabel = New-Object Windows.Controls.TextBlock $descriptionLabel.Text = "Description" $descriptionLabel.Margin = "5,5,5,5" $parameterstack.Children.Add($descriptionLabel) | Out-Null $descriptionBox = New-Object Windows.Controls.TextBox $descriptionBox.Text = $commandDetails.Description $descriptionBox.Margin = "5,5,5,5" $parameterstack.Children.Add($descriptionBox) | Out-Null $submitButton.Add_Click({ foreach ($paramName in $textBoxes.Keys) { $commandDetails.Parameters.$paramName.Value = $textBoxes[$paramName].Text Write-Verbose "Parameter '$paramName' updated with value '$($textBoxes[$paramName].Text)'" } $commandDetails.Description = $descriptionBox.Text Write-Verbose "Description updated to '$($descriptionBox.Text)'" $window.DialogResult = $true $window.Close() }) $cancelButton.Add_Click({ Write-Verbose "User canceled the update." $window.DialogResult = $false $window.Close() }) $helpButton.Add_Click({ try { [System.Windows.Input.Mouse]::OverrideCursor = [System.Windows.Input.Cursors]::Wait try { Get-Help $CommandName -Online } finally { [System.Windows.Input.Mouse]::OverrideCursor = $null # Reset the cursor } } catch { Write-Warning "Failed to open online help for command '$CommandName'." [System.Windows.MessageBox]::Show("Failed to open online help for command '$CommandName'.", "Warning", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) | Out-Null } }) $result = $window.ShowDialog() if (-not $result) { Write-Verbose "No changes made."; return } } # Apply passed Parameters directly if GUI is not used else { foreach ($paramName in $parameters.Keys) { if ($commandDetails.Parameters.$paramName) { $commandDetails.Parameters.$paramName.Value = $parameters[$paramName] Write-Verbose "Parameter '$paramName' updated with value '$($parameters[$paramName])'" } else { Write-Warning "Parameter '$paramName' not found in the command '$CommandName'." } } # Update description if provided if ($Description) { $commandDetails.Description = $Description Write-Verbose "Description updated to '$Description'" } } foreach ($paramName in $commandDetails.Parameters.PSObject.Properties.Name) { $paramDetails = $commandDetails.Parameters.$paramName if ($paramDetails -is [hashtable]) { $commandDetails.Parameters.$paramName = Convert-HashtableToString -hash $paramDetails } } $jsonConfig = $config | ConvertTo-Json -Depth 4 Set-Content -Path $ConfigFilePath -Value $jsonConfig Write-Verbose "Parameter values for command '$CommandName' updated in module '$ModuleName' and saved to $ConfigFilePath" } } |