Hashtable.psm1
[CmdletBinding()] param() $baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath) $script:PSModuleInfo = Test-ModuleManifest -Path "$PSScriptRoot\$baseName.psd1" $script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ } $scriptName = $script:PSModuleInfo.Name Write-Debug "[$scriptName] - Importing module" #region [functions] - [public] Write-Debug "[$scriptName] - [functions] - [public] - Processing folder" #region [functions] - [public] - [ConvertFrom-Hashtable] Write-Debug "[$scriptName] - [functions] - [public] - [ConvertFrom-Hashtable] - Importing" filter ConvertFrom-Hashtable { <# .SYNOPSIS Converts a hashtable to a PSCustomObject. .DESCRIPTION Recursively converts a hashtable to a PSCustomObject. This function is useful for converting structured data to objects, making it easier to work with and manipulate. .EXAMPLE $hashtable = @{ Name = 'John Doe' Age = 30 Address = @{ Street = '123 Main St' City = 'Somewhere' ZipCode = '12345' } Occupations = @( @{ Title = 'Developer' Company = 'TechCorp' }, @{ Title = 'Consultant' Company = 'ConsultCorp' } ) } ConvertFrom-Hashtable -InputObject $hashtable Output: ```powershell Name Value ---- ----- Age 30 Address @{ZipCode=12345; City=Somewhere; Street=123 Main St} Name John Doe Occupations {@{Title=Developer; Company=TechCorp}, @{Title=Consultant; Company=ConsultCorp}} ``` Converts the provided hashtable into a PSCustomObject. .OUTPUTS PSCustomObject .NOTES A custom object representation of the provided hashtable. The returned object preserves the original structure of the input. .LINK https://psmodule.io/Hashtable/Functions/ConvertFrom-Hashtable #> [OutputType([PSCustomObject])] [CmdletBinding()] param( # The hashtable to convert to a PSCustomObject. [Parameter(Mandatory, ValueFromPipeline)] [hashtable] $InputObject ) # Prepare a hashtable to hold properties for the PSCustomObject. $props = @{} foreach ($key in $InputObject.Keys) { $value = $InputObject[$key] if ($value -is [hashtable]) { # Recursively convert nested hashtables. $props[$key] = $value | ConvertFrom-Hashtable } elseif ($value -is [array]) { # Check each element: if it's a hashtable, convert it; otherwise, leave it as is. $props[$key] = $value | ForEach-Object { if ($_ -is [hashtable]) { $_ | ConvertFrom-Hashtable } else { $_ } } } else { # For other types, assign directly. $props[$key] = $value } } [pscustomobject]$props } Write-Debug "[$scriptName] - [functions] - [public] - [ConvertFrom-Hashtable] - Done" #endregion [functions] - [public] - [ConvertFrom-Hashtable] #region [functions] - [public] - [ConvertTo-HashTable] Write-Debug "[$scriptName] - [functions] - [public] - [ConvertTo-HashTable] - Importing" filter ConvertTo-Hashtable { <# .SYNOPSIS Converts an object to a hashtable. .DESCRIPTION Recursively converts an object to a hashtable. This function is useful for converting complex objects to hashtables for serialization or other purposes. .EXAMPLE $object = [PSCustomObject]@{ Name = 'John Doe' Age = 30 Address = [PSCustomObject]@{ Street = '123 Main St' City = 'Somewhere' ZipCode = '12345' } Occupations = @( [PSCustomObject]@{ Title = 'Developer' Company = 'TechCorp' }, [PSCustomObject]@{ Title = 'Consultant' Company = 'ConsultCorp' } ) } ConvertTo-Hashtable -InputObject $object Output: ```powershell Name Value ---- ----- Age 30 Address {[ZipCode, 12345], [City, Somewhere], [Street, 123 Main St]} Name John Doe Occupations {@{Title=Developer; Company=TechCorp}, @{Title=Consultant; Company=ConsultCorp}} ``` This returns a hashtable representation of the object. .OUTPUTS hashtable .NOTES The function returns a hashtable representation of the input object, converting complex nested structures recursively. .LINK https://psmodule.io/Hashtable/Functions/ConvertTo-Hashtable #> [OutputType([hashtable])] [CmdletBinding()] param ( # The object to convert to a hashtable. [Parameter( Mandatory, ValueFromPipeline )] [PSObject] $InputObject ) $hashtable = @{} # Iterate over each property of the object $InputObject.PSObject.Properties | ForEach-Object { $propertyName = $_.Name $propertyValue = $_.Value if ($propertyValue -is [PSObject]) { if ($propertyValue -is [Array] -or $propertyValue -is [System.Collections.IEnumerable]) { # Handle arrays and enumerables $hashtable[$propertyName] = @() foreach ($item in $propertyValue) { $hashtable[$propertyName] += ConvertTo-Hashtable -InputObject $item } } elseif ($propertyValue.PSObject.Properties.Count -gt 0) { # Handle nested objects $hashtable[$propertyName] = ConvertTo-Hashtable -InputObject $propertyValue } else { # Handle simple properties $hashtable[$propertyName] = $propertyValue } } else { $hashtable[$propertyName] = $propertyValue } } $hashtable } Write-Debug "[$scriptName] - [functions] - [public] - [ConvertTo-HashTable] - Done" #endregion [functions] - [public] - [ConvertTo-HashTable] #region [functions] - [public] - [Export-Hashtable] Write-Debug "[$scriptName] - [functions] - [public] - [Export-Hashtable] - Importing" filter Export-Hashtable { <# .SYNOPSIS Exports a hashtable to a specified file in PSD1, PS1, or JSON format. .DESCRIPTION This function takes a hashtable and exports it to a file in one of the supported formats: PSD1, PS1, or JSON. The format is determined based on the file extension provided in the Path parameter. If the extension is not recognized, the function throws an error. This function supports pipeline input. .EXAMPLE $myHashtable = @{ Key = 'Value'; Number = 42 } $myHashtable | Export-Hashtable -Path 'C:\config.psd1' Exports the hashtable to a PSD1 file. .EXAMPLE $myHashtable = @{ Key = 'Value'; Number = 42 } Export-Hashtable -Hashtable $myHashtable -Path 'C:\script.ps1' Exports the hashtable as a PowerShell script that returns the hashtable when executed. .EXAMPLE $myHashtable = @{ Key = 'Value'; Number = 42 } Export-Hashtable -Hashtable $myHashtable -Path 'C:\data.json' Exports the hashtable as a JSON file. .OUTPUTS void .NOTES This function does not return an output. It writes the exported data to a file. .LINK https://psmodule.io/Export/Functions/Export-Hashtable/ #> [OutputType([void])] [CmdletBinding()] param( # The hashtable to export. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [hashtable] $Hashtable, # The file path where the hashtable will be exported. [Parameter(Mandatory)] [string] $Path ) # Determine file extension and select export format. $extension = [System.IO.Path]::GetExtension($Path).ToLower() switch ($extension) { '.psd1' { try { # Use Format-Hashtable to generate a PSD1-like output. $formattedHashtable = Format-Hashtable -Hashtable $Hashtable Set-Content -Path $Path -Value $formattedHashtable -Force } catch { throw "Failed to export hashtable to PSD1 file '$Path'. Error details: $_" } } '.ps1' { try { # Format the hashtable and wrap it in a function so that when the script is run it returns the hashtable. $formattedHashtable = Format-Hashtable -Hashtable $Hashtable Set-Content -Path $Path -Value $formattedHashtable -Force } catch { throw "Failed to export hashtable to PS1 file '$Path'. Error details: $_" } } '.json' { try { # Convert the hashtable to JSON. You might adjust the Depth parameter as needed. $jsonContent = $Hashtable | ConvertTo-Json -Depth 10 Set-Content -Path $Path -Value $jsonContent -Force } catch { throw "Failed to export hashtable to JSON file '$Path'. Error details: $_" } } default { throw "Unsupported file extension '$extension'. Only .psd1, .ps1, and .json files are supported." } } } Write-Debug "[$scriptName] - [functions] - [public] - [Export-Hashtable] - Done" #endregion [functions] - [public] - [Export-Hashtable] #region [functions] - [public] - [Format-Hashtable] Write-Debug "[$scriptName] - [functions] - [public] - [Format-Hashtable] - Importing" filter Format-Hashtable { <# .SYNOPSIS Converts a hashtable to its PowerShell code representation. .DESCRIPTION Recursively converts a hashtable to its PowerShell code representation. This function is useful for exporting hashtables to `.psd1` files, making it easier to store and retrieve structured data. .EXAMPLE $hashtable = @{ Key1 = 'Value1' Key2 = @{ NestedKey1 = 'NestedValue1' NestedKey2 = 'NestedValue2' } Key3 = @(1, 2, 3) Key4 = $true } Format-Hashtable -Hashtable $hashtable Output: ```powershell @{ Key1 = 'Value1' Key2 = @{ NestedKey1 = 'NestedValue1' NestedKey2 = 'NestedValue2' } Key3 = @( 1 2 3 ) Key4 = $true } ``` .OUTPUTS string .NOTES A string representation of the given hashtable. Useful for serialization and exporting hashtables to files. .LINK https://psmodule.io/Hashtable/Functions/Format-Hashtable #> [OutputType([string])] [CmdletBinding()] param ( # The hashtable to convert to a PowerShell code representation. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [object] $Hashtable, # The indentation level for formatting nested structures. [Parameter()] [int] $IndentLevel = 1 ) # If the hashtable is empty, return '@{}' immediately. if ($Hashtable -is [System.Collections.IDictionary] -and $Hashtable.Count -eq 0) { return '@{}' } $indent = ' ' $lines = @() $lines += '@{' $levelIndent = $indent * $IndentLevel # Compute maximum key length at this level to align the '=' characters $maxKeyLength = ($Hashtable.Keys | ForEach-Object { $_.ToString().Length } | Measure-Object -Maximum).Maximum foreach ($key in $Hashtable.Keys) { # Pad each key to the maximum length so the '=' lines up. $paddedKey = $key.ToString().PadRight($maxKeyLength) Write-Verbose "Processing key: [$key]" $value = $Hashtable[$key] Write-Verbose "Processing value: [$value]" if ($null -eq $value) { Write-Verbose "Value type: `$null" $lines += "$levelIndent$paddedKey = `$null" continue } Write-Verbose "Value type: [$($value.GetType().Name)]" if (($value -is [System.Collections.Hashtable]) -or ($value -is [System.Collections.Specialized.OrderedDictionary])) { $nestedString = Format-Hashtable -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$levelIndent$paddedKey = $nestedString" } elseif ($value -is [System.Management.Automation.PSCustomObject]) { $nestedString = Format-Hashtable -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$levelIndent$paddedKey = $nestedString" } elseif ($value -is [System.Management.Automation.PSObject]) { $nestedString = Format-Hashtable -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$levelIndent$paddedKey = $nestedString" } elseif ($value -is [bool]) { $lines += "$levelIndent$paddedKey = `$$($value.ToString().ToLower())" } elseif ($value -is [int] -or $value -is [double]) { $lines += "$levelIndent$paddedKey = $value" } elseif ($value -is [array]) { if ($value.Count -eq 0) { $lines += "$levelIndent$paddedKey = @()" } else { $lines += "$levelIndent$paddedKey = @(" $arrayIndent = $levelIndent + $indent # Increase indentation for elements inside @(...) $value | ForEach-Object { $nestedValue = $_ Write-Verbose "Processing array element: $_" Write-Verbose "Element type: $($_.GetType().Name)" if (($nestedValue -is [System.Collections.Hashtable]) -or ($nestedValue -is [System.Collections.Specialized.OrderedDictionary])) { $nestedString = Format-Hashtable -Hashtable $nestedValue -IndentLevel ($IndentLevel + 2) $lines += "$arrayIndent$nestedString" } elseif ($nestedValue -is [bool]) { $lines += "$arrayIndent`$$($nestedValue.ToString().ToLower())" } elseif ($nestedValue -is [int]) { $lines += "$arrayIndent$nestedValue" } else { $lines += "$arrayIndent'$nestedValue'" } } $arrayIndent = $levelIndent $lines += "$arrayIndent)" } } else { $value = $value -replace "('+)", "''" # Escape single quotes in a manifest file $lines += "$levelIndent$paddedKey = '$value'" } } $levelIndent = $indent * ($IndentLevel - 1) $lines += "$levelIndent}" return $lines -join [Environment]::NewLine } Write-Debug "[$scriptName] - [functions] - [public] - [Format-Hashtable] - Done" #endregion [functions] - [public] - [Format-Hashtable] #region [functions] - [public] - [Import-Hashtable] Write-Debug "[$scriptName] - [functions] - [public] - [Import-Hashtable] - Importing" filter Import-Hashtable { <# .SYNOPSIS Imports a hashtable from a specified file. .DESCRIPTION This function reads a file and imports its contents as a hashtable. It supports `.psd1`, `.ps1`, and `.json` files. - `.psd1` files are imported using `Import-PowerShellDataFile`. This process is safe and does not execute any code. - `.ps1` scripts are executed, and their output must be a hashtable. If the script does not return a hashtable, an error is thrown. - `.json` files are read and converted to a hashtable using `ConvertFrom-Json -AsHashtable`. This process is safe and does not execute any code. If the specified file does not exist or has an unsupported format, an error is thrown. .EXAMPLE Import-Hashtable -Path 'C:\config.psd1' Output: ```powershell Name Value ---- ----- Setting1 Enabled Setting2 42 ``` Imports a hashtable from a `.psd1` file. .EXAMPLE Import-Hashtable -Path 'C:\script.ps1' Output: ```powershell Name Value ---- ----- Key1 Value1 Key2 Value2 ``` Executes the script and imports the hashtable returned by the `.ps1` file. .EXAMPLE Import-Hashtable -Path 'C:\data.json' Output: ```powershell Name Value ---- ----- username johndoe roles {Admin, User} ``` Reads a JSON file and converts its content into a hashtable. .OUTPUTS hashtable .NOTES A hashtable containing the data from the imported file. The hashtable structure depends on the contents of the imported file. .LINK https://psmodule.io/Hashtable/Functions/Import-Hashtable #> [CmdletBinding()] param( # Path to the file containing the hashtable. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [string] $Path ) if (-not (Test-Path -Path $Path)) { throw "File '$Path' does not exist." } $extension = [System.IO.Path]::GetExtension($Path).ToLower() switch ($extension) { '.psd1' { try { $hashtable = Import-PowerShellDataFile -Path $Path } catch { throw "Failed to import hashtable from PSD1 file '$Path'. Error details: $_" } } '.ps1' { try { $hashtable = & $Path if (-not ($hashtable -is [hashtable])) { throw 'The PS1 script did not return a hashtable. Verify its content.' } } catch { throw "Failed to import hashtable from PS1 file '$Path'. Error details: $_" } } '.json' { try { # Read the entire JSON file and convert it to a hashtable. $jsonContent = Get-Content -Path $Path -Raw $hashtable = $jsonContent | ConvertFrom-Json -AsHashtable } catch { throw "Failed to import hashtable from JSON file '$Path'. Error details: $_" } } default { throw "Unsupported file extension '$extension'. Only .psd1, .ps1, and .json files are supported." } } return $hashtable } Write-Debug "[$scriptName] - [functions] - [public] - [Import-Hashtable] - Done" #endregion [functions] - [public] - [Import-Hashtable] #region [functions] - [public] - [Merge-Hashtable] Write-Debug "[$scriptName] - [functions] - [public] - [Merge-Hashtable] - Importing" filter Merge-Hashtable { <# .SYNOPSIS Merges multiple hashtables, applying overrides in sequence. .DESCRIPTION This function takes a primary hashtable (`$Main`) and merges it with one or more override hashtables (`$Overrides`). Overrides are applied in order, with later values replacing earlier ones if the same key exists. If the `-Force` switch is used, values will be overridden even if they are empty or `$null`. The resulting hashtable is returned. .EXAMPLE $Main = @{ Key1 = 'Value1' Key2 = 'Value2' } $Override1 = @{ Key2 = 'Override2' } $Override2 = @{ Key3 = 'Value3' } $Main | Merge-Hashtable -Overrides $Override1, $Override2 Output: ```powershell Name Value ---- ----- Key1 Value1 Key2 Override2 Key3 Value3 ``` Merges `$Main` with two override hashtables, applying overrides in order. .EXAMPLE $Main = @{ Key1 = 'Value1' Key2 = 'Value2' } $Override = @{ Key2 = '' Key3 = 'Value3' } $Main | Merge-Hashtable -Overrides $Override -Force Output: ```powershell Name Value ---- ----- Key1 Value1 Key2 Key3 Value3 ``` Forces overriding even if the value is empty. .OUTPUTS Hashtable .NOTES A merged hashtable with applied overrides. .LINK https://psmodule.io/Hashtable/Functions/Merge-Hashtable/ #> [OutputType([Hashtable])] [Alias('Join-Hashtable')] [CmdletBinding()] param ( # Main hashtable [Parameter(Mandatory)] [hashtable] $Main, # Hashtable with overrides. # Providing a list of overrides will apply them in order. # Last write wins. [Parameter( Mandatory, ValueFromPipeline )] [hashtable[]] $Overrides, # When specified, force override even if the value is empty or null. [Parameter()] [switch] $Force ) begin { $Output = $Main.Clone() } process { foreach ($Override in $Overrides) { foreach ($Key in $Override.Keys) { if (($Output.Keys) -notcontains $Key) { $Output.$Key = $Override.$Key } if ($Force -or -not [string]::IsNullOrEmpty($Override[$Key])) { $Output[$Key] = $Override[$Key] } } } } end { return $Output } } Write-Debug "[$scriptName] - [functions] - [public] - [Merge-Hashtable] - Done" #endregion [functions] - [public] - [Merge-Hashtable] #region [functions] - [public] - [Remove-HashtableEntry] Write-Debug "[$scriptName] - [functions] - [public] - [Remove-HashtableEntry] - Importing" filter Remove-HashtableEntry { <# .SYNOPSIS Removes specific entries from a hashtable based on given criteria. .DESCRIPTION This function filters out entries from a hashtable based on different conditions. You can remove keys with null or empty values, keys of a specific type, or keys matching certain names. It also allows keeping entries based on the opposite criteria. If the `-All` parameter is used, all entries in the hashtable will be removed. .EXAMPLE $myHashtable = @{ Name = 'John'; Age = 30; Country = $null } $myHashtable | Remove-HashtableEntry -NullOrEmptyValues Output: ```powershell @{ Name = 'John'; Age = 30 } ``` Removes entries with null or empty values from the hashtable. .EXAMPLE $myHashtable = @{ Name = 'John'; Age = 30; Active = $true } $myHashtable | Remove-HashtableEntry -Types 'Boolean' Output: ```powershell @{ Name = 'John'; Age = 30 } ``` Removes entries where the value type is Boolean. .EXAMPLE $myHashtable = @{ Name = 'John'; Age = 30; Country = 'USA' } $myHashtable | Remove-HashtableEntry -Keys 'Age' Output: ```powershell @{ Name = 'John'; Country = 'USA' } ``` Removes the key 'Age' from the hashtable. .OUTPUTS void .NOTES The function modifies the input hashtable but does not return output. .LINK https://psmodule.io/Hashtable/Functions/Remove-HashtableEntry/ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function does not change state.' )] [OutputType([void])] [CmdletBinding()] param( # The hashtable to remove entries from. [Parameter(Mandatory, ValueFromPipeline)] [hashtable] $Hashtable, # Remove keys with null or empty values. [Parameter()] [switch] $NullOrEmptyValues, # Remove keys of a specified type. [Parameter()] [string[]] $Types, # Remove keys with a specified name. [Parameter()] [Alias('Names')] [string[]] $Keys, # Remove keys with null or empty values. [Parameter()] [Alias('IgnoreNullOrEmptyValues')] [switch] $KeepNullOrEmptyValues, # Keep only keys of a specified type. [Parameter()] [Alias('IgnoreTypes')] [string[]] $KeepTypes, # Keep only keys with a specified name. [Parameter()] [Alias('IgnoreKey', 'KeepNames')] [string[]] $KeepKeys, # Remove all entries from the hashtable. [Parameter()] [switch] $All ) # Copy keys to a static array to prevent modifying the collection during iteration. $hashtableKeys = @($Hashtable.Keys) foreach ($key in $hashtableKeys) { $value = $Hashtable[$key] $vaultIsNullOrEmpty = [string]::IsNullOrEmpty($value) $valueIsNotNullOrEmpty = -not $vaultIsNullOrEmpty $typeName = if ($valueIsNotNullOrEmpty) { $value.GetType().Name } else { $null } if ($KeepKeys -and $key -in $KeepKeys) { Write-Debug "Keeping [$key] because it is in KeepKeys [$KeepKeys]." } elseif ($KeepTypes -and $typeName -in $KeepTypes) { Write-Debug "Keeping [$key] because its type [$typeName] is in KeepTypes [$KeepTypes]." } elseif ($vaultIsNullOrEmpty -and $KeepNullOrEmptyValues) { Write-Debug "Keeping [$key] because its value is null or empty." } elseif ($vaultIsNullOrEmpty -and $NullOrEmptyValues) { Write-Debug "Removing [$key] because its value is null or empty." $Hashtable.Remove($key) } elseif ($Types -and $typeName -in $Types) { Write-Debug "Removing [$key] because its type [$typeName] is in Types [$Types]." $Hashtable.Remove($key) } elseif ($Keys -and $key -in $Keys) { Write-Debug "Removing [$key] because it is in Keys [$Keys]." $Hashtable.Remove($key) } elseif ($All) { Write-Debug "Removing [$key] because All flag is set." $Hashtable.Remove($key) } else { Write-Debug "Keeping [$key] by default." } } } Write-Debug "[$scriptName] - [functions] - [public] - [Remove-HashtableEntry] - Done" #endregion [functions] - [public] - [Remove-HashtableEntry] Write-Debug "[$scriptName] - [functions] - [public] - Done" #endregion [functions] - [public] #region Member exporter $exports = @{ Alias = '*' Cmdlet = '' Function = @( 'ConvertFrom-Hashtable' 'ConvertTo-HashTable' 'Export-Hashtable' 'Format-Hashtable' 'Import-Hashtable' 'Merge-Hashtable' 'Remove-HashtableEntry' ) } Export-ModuleMember @exports #endregion Member exporter |