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/ConvertTo/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] - [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 } ``` Converts the provided hashtable into a PowerShell-formatted string representation. .OUTPUTS string .NOTES A string representation of the given hashtable. Useful for serialization and exporting hashtables to files. .LINK https://psmodule.io/Format/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 foreach ($key in $Hashtable.Keys) { Write-Verbose "Processing key: $key" $value = $Hashtable[$key] Write-Verbose "Processing value: $value" if ($null -eq $value) { Write-Verbose "Value type: `$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$key = $nestedString" } elseif ($value -is [System.Management.Automation.PSCustomObject]) { $nestedString = Format-Hashtable -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$levelIndent$key = $nestedString" } elseif ($value -is [System.Management.Automation.PSObject]) { $nestedString = Format-Hashtable -Hashtable $value -IndentLevel ($IndentLevel + 1) $lines += "$levelIndent$key = $nestedString" } elseif ($value -is [bool]) { $lines += "$levelIndent$key = `$$($value.ToString().ToLower())" } elseif ($value -is [int] -or $value -is [double]) { $lines += "$levelIndent$key = $value" } elseif ($value -is [array]) { if ($value.Count -eq 0) { $lines += "$levelIndent$key = @()" } else { $lines += "$levelIndent$key = @(" $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$key = '$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] - [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 value, type, or name. .DESCRIPTION This version applies keep filters with the highest precedence. If a key qualifies based on the provided Keep parameters (KeepTypes and/or KeepKeys), it is preserved no matter what removal conditions might say. If no keep filters are provided, the function applies removal conditions: - NullOrEmptyValues: Remove keys with null or empty values. - RemoveTypes: Remove keys whose values are of the specified type(s). - RemoveKeys: Remove keys with the specified name(s). When Keep filters are provided, only keys that match ALL specified keep criteria will be preserved; keys that do not match are removed regardless of removal settings. At the end, the original hashtable is cleared and repopulated with the filtered results. .EXAMPLE $ht = @{ KeepThis = 'Value1' RemoveThis = 'Delete' Other = 42 } $ht | Remove-HashtableEntry -KeepKeys 'KeepThis' -RemoveKeys 'RemoveThis' This will keep only the key "KeepThis", regardless of other removal flags. .OUTPUTS hashtable .NOTES The function modifies the input hashtable in place. #> [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' 'Format-Hashtable' 'Merge-Hashtable' 'Remove-HashtableEntry' ) } Export-ModuleMember @exports #endregion Member exporter |