private/ConvertTo-VSAHashtable.ps1
|
function ConvertTo-VSAHashtable { <# .SYNOPSIS Normalizes a nested-object argument to a [hashtable]. .DESCRIPTION Accepts a nested structure in any of the forms VSA cmdlets receive it and returns a plain [hashtable] ready to place into a request body: - [hashtable] / [System.Collections.IDictionary] -- returned as a hashtable (the idiomatic, preferred input). - [pscustomobject] / [psobject] -- each NoteProperty becomes a key. - [string] (legacy) -- two historical micro-syntaxes are accepted: * brace form "{ Key1= val1; Key2= val2 }" (as used by New-VSAOrganization, New-VSATenant LicenseValues) * plain form "Key1=val1`nKey2=val2" (ConvertFrom-StringData form, as used by the *Attributes* parameters of the other cmdlets) Both are parsed by the same hardened logic: optional surrounding braces are stripped, pairs are split on ';' OR newlines, and each pair is split on the FIRST '=' only so a value may itself contain '='. Unlike the previous ConvertFrom-StringData approach this does not treat '\' as an escape introducer and does not depend on the pipeline-global $Matches automatic variable. Empty / whitespace-only keys and $null inputs yield an empty hashtable. Empty VALUES are preserved; callers that want empty values pruned should do so after conversion (the write cmdlets already prune empty keys from the assembled body). .PARAMETER InputObject The nested structure to normalize. May be $null. .EXAMPLE ConvertTo-VSAHashtable @{ City = 'New York'; PrimaryEmail = 'a@b.com' } .EXAMPLE ConvertTo-VSAHashtable '{ City= New York; PrimaryPhone= 555;x99 }' .OUTPUTS [hashtable] #> [CmdletBinding()] [OutputType([hashtable])] param( [parameter(Mandatory = $true, Position = 0)] [AllowNull()] [object] $InputObject ) if ($null -eq $InputObject) { return @{} } # Already a dictionary -- the elegant, preferred path. Copy into a fresh hashtable so callers # can safely mutate the result without touching the argument they passed in. if ($InputObject -is [System.Collections.IDictionary]) { $ht = @{} foreach ($key in $InputObject.Keys) { $ht[$key] = $InputObject[$key] } return $ht } # PSCustomObject / PSObject with note properties. if ($InputObject -is [psobject] -and -not ($InputObject -is [string]) -and $InputObject.PSObject.Properties.Count -gt 0) { $ht = @{} foreach ($prop in $InputObject.PSObject.Properties) { $ht[$prop.Name] = $prop.Value } return $ht } # Legacy string form (both brace and plain variants). [string] $text = "$InputObject" if ([string]::IsNullOrWhiteSpace($text)) { return @{} } # Strip a single optional pair of surrounding braces without regex (regex '{(.*?)\}' was # brace-blind and truncated on any '}' inside a value). $text = $text.Trim() if ($text.StartsWith('{') -and $text.EndsWith('}')) { $text = $text.Substring(1, $text.Length - 2) } $result = @{} foreach ($pair in ($text -split '[;\r\n]+')) { if ([string]::IsNullOrWhiteSpace($pair)) { continue } $eq = $pair.IndexOf('=') if ($eq -lt 0) { continue } # not a key=value pair -- skip $key = $pair.Substring(0, $eq).Trim() if ([string]::IsNullOrEmpty($key)) { continue } $value = $pair.Substring($eq + 1).Trim() # first '=' only -> value keeps any '=' $result[$key] = $value } return $result } |