Public/ConfigCommands/Import-RpConfig.ps1
function Import-RpConfig { <# .SYNOPSIS Imports and processes a configuration JSON file for RemotePro. .DESCRIPTION This function reads a JSON configuration file, converts it into a PowerShell object, and processes each module and command defined in the file. It returns a hashtable where each key represents a module, and the value is another hashtable containing the module's commands. Each command object includes properties like CommandName, Id, Description, and Parameters, as well as a method FormatCommandObject to prepare the command for execution. The command object can be accessed using dot notation for easy navigation, and the FormatCommandObject method allows you to add or modify parameters before execution. Using Invoke-RpCommandObject, you can execute the formatted command locally where as remotely is experimental. .COMPONENT ConfigCommands .PARAMETER ConfigFilePath The path to the configuration JSON file. If not provided, the function uses the default path returned by the `Get-RpConfigPath` function. .OUTPUTS Hashtable Returns a hashtable of modules, where each module contains commands as nested objects. .EXAMPLE $modules = Import-RpConfig -ConfigFilePath $(Get-RpConfigPath) Import configuration from a specified file. .EXAMPLE $modules = Import-RpConfig Import configuration using the default path. .EXAMPLE $commandObject = $modules.RemotePro.'Get-RpVmsHardwareCustom'.'1234-5678' Access and format a specific command object. .EXAMPLE & $preparedCommand.CommandName @($preparedCommand.Parameters) Execute the formatted command locally using the call operator. .EXAMPLE Calling Get-RpVmsItemStateCustom from the default config commands. $commandId = (Get-RpDefaultConfigCommandDetails).'Get-RpVmsItemStateCustom'.Id $preparedCommand1 = (Get-RpConfigCommands -All).'Get-RpVmsItemStateCustom'.$commandId.FormatCommandObject($commandId) Calling Out-HtmlView from the default config commands. $commandId = (Get-RpDefaultConfigCommandDetails).'Out-HtmlView'.Id $preparedCommand2 = (Get-RpConfigCommands -All).'Out-HtmlView'.$commandId.FormatCommandObject($commandId) Invoke default config commands. Invoke-RpCommandObject -CommandObject $preparedCommand1 | Invoke-RpCommandObject -CommandObject $preparedCommand2 .EXAMPLE $preparedCommand | Invoke-RpCommandObject Use Invoke-RpCommandObject for execution. .EXAMPLE $preparedCommand | Invoke-RpCommandObject -UseInvokeCommand -ComputerName "RemoteServer" Uses Invoke-RpCommandObject for remote execution. .NOTES - Modules and commands are structured as nested hashtables for easy navigation. - The FormatCommandObject method dynamically adds or modifies parameters. - Ensure the configuration JSON file follows the required schema for modules and commands. - Use dot notation to navigate the resulting $modules hashtable. .LINK https://www.remotepro.dev/en-US/Import-RpConfig #> [CmdletBinding()] param ( [string]$ConfigFilePath # Path to the configuration JSON file ) begin { Add-Type -AssemblyName PresentationFramework # Use appdata path if there is not a filepath value. if (-not ($ConfigFilePath)){ $ConfigFilePath = Get-RpConfigPath } } process { # Load the JSON configuration file into a string $configContent = Get-Content -Path $ConfigFilePath -Raw Write-Verbose "Config content loaded from file." # Convert the JSON string into a PowerShell object $config = $configContent | ConvertFrom-Json Write-Verbose "Config successfully converted from JSON." # Create a hashtable to hold modules and commands $modules = @{} # Iterate over each module in the configuration foreach ($moduleName in $config.ConfigCommands.PSObject.Properties.Name) { $moduleName.PSObject.TypeNames.Insert(0,"$moduleName") Write-Verbose "Processing module: $moduleName" $moduleCommands = @{} # Each module will have a collection of commands $moduleCommands.PSObject.TypeNames.Insert(0,"$moduleName.ConfigCommands") # Iterate over each command in the module foreach ($commandDetails in $config.ConfigCommands.$moduleName) { Write-Verbose "Processing command: $($commandDetails.CommandName) with ID $($commandDetails.Id)" $commandDetails.PSObject.TypeNames.Insert(0,"$moduleName.ConfigCommands.$CommandName") # Create a custom object to hold the command details $commandObject = [pscustomobject]@{ ModuleName = $commandDetails.ModuleName CommandName = $commandDetails.CommandName Id = $commandDetails.Id Description = $commandDetails.Description Parameters = $commandDetails.Parameters # Nested parameters for each command } $commandObject.PSObject.TypeNames.Insert(0,"$moduleName.ConfigCommands.$(($commandDetails.CommandName))[$(($commandDetails.Id))].ModuleName") $commandObject.PSObject.TypeNames.Insert(0,"$moduleName.ConfigCommands.$(($commandDetails.CommandName))[$(($commandDetails.Id))]") $commandObject.Id.PSObject.TypeNames.Insert(0,"$moduleName.ConfigCommands.$(($commandDetails.CommandName))[$(($commandDetails.Id))].Id.String") $commandObject.Parameters.PSObject.TypeNames.Insert(0,"$moduleName.ConfigCommands.$(($commandDetails.CommandName)).Parameters.PSCustomObject") $commandObject | Add-Member -MemberType ScriptMethod -Name "FormatCommandObject" -Force -Value { param ( [String]$Id, # Optional command ID [Hashtable]$AdditionalParameters = @{} # Additional parameters to merge ) # Default to the CommandObject's ID if none is provided if (-not $Id) { Write-Verbose "No ID provided. Defaulting to CommandObject's ID: $($this.Id)" $Id = $this.Id } Write-Verbose "Checking if command ID matches. Provided: $Id, CommandObject ID: $($this.Id)" if ([String]$this.Id -eq $Id) { Write-Verbose "Command '$($this.CommandName)' with ID $Id matched. Preparing the command object..." # Create a static copy of parameters as a plain hashtable $staticParameters = [Hashtable]::new() foreach ($property in $this.Parameters.PSObject.Properties) { $staticParameters[$property.Name] = $property.Value } # Construct the base parameter hashtable $paramHash = @{} foreach ($paramName in $staticParameters.Keys) { $paramConfig = $staticParameters[$paramName] # Handle Boolean string conversion if ($paramConfig -is [string] -and ($paramConfig -eq 'true' -or $paramConfig -eq 'false')) { $paramHash[$paramName] = [bool]::Parse($paramConfig) } # Handle key-value pairs in strings elseif ($paramConfig -is [string] -and $paramConfig.StartsWith('@{')) { try { # Parse key-value pairs from the string $paramValue = ($paramConfig -split ';') | ForEach-Object { if ($_ -match 'Value=(.*)$') { $matches[1].Trim('}') } } if ($null -ne $paramValue -and -not [string]::IsNullOrEmpty($paramValue)) { $paramHash[$paramName] = $paramValue } } catch { Write-Warning "Skipping malformed parameter '$paramName'. Error: $_" } } # Handle direct values elseif ($null -ne $paramConfig) { $paramHash[$paramName] = $paramConfig } } Write-Verbose "Base parameters for command '$($this.CommandName)': $($paramHash | Out-String)" # Merge additional parameters if ($AdditionalParameters) { Write-Verbose "Adding additional parameters: $($AdditionalParameters | Out-String)" foreach ($key in $AdditionalParameters.Keys) { $paramHash[$key] = $AdditionalParameters[$key] } } # Convert Boolean-like strings in additional parameters foreach ($key in $($paramHash.Keys)) { if ($paramHash[$key] -is [string] -and ($paramHash[$key] -eq 'true' -or $paramHash[$key] -eq 'false')) { $paramHash[$key] = [bool]::Parse($paramHash[$key]) } } Write-Verbose "Final parameters for command '$($this.CommandName)': $($paramHash | Out-String)" # Return a reusable object return [pscustomobject]@{ CommandName = $this.CommandName Parameters = $paramHash } } else { Write-Error "Command ID $Id does not match CommandObject ID $($this.Id)." } } # Store the command object by both CommandName and Id if (-not $moduleCommands.ContainsKey($commandDetails.CommandName)) { $moduleCommands[$commandDetails.CommandName] = @{} } # Store command object keyed by its unique Id $moduleCommands[$commandDetails.CommandName][$commandDetails.Id] = $commandObject } # Add the processed module to the main hashtable $modules[$moduleName] = $moduleCommands } # Return the final modules hashtable return $modules } } |