Utilities/Build-CallableSplat.ps1
# Cache for parameter metadata to avoid repeated Get-Command lookups if (-not $script:__ParamCache) { $script:__ParamCache = @{} } function Build-CallableSplat { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$CommandName, # Optional: a PSCustomObject (e.g., provider Config) [pscustomobject]$Config, # Optional: extra runtime args (hashtable) [hashtable]$Arguments, # If set, items in -Arguments override same-named values from -Config [switch]$PreferArgs, # Pass null/empty strings through (default: skip) [switch]$IncludeNulls, # Emit verbose logging (use -Verbose to see output) [switch]$Log, # Optional prefix to make logs searchable [string]$LogPrefix = '' ) if (-not $script:__ParamCache.ContainsKey($CommandName)) { $cmd = Get-Command -Name $CommandName -CommandType Function, Cmdlet, ExternalScript -ErrorAction Stop $names = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $aliasMap = @{} # alias -> canonical foreach ($p in $cmd.Parameters.Values) { $null = $names.Add($p.Name) foreach ($a in $p.Aliases) { $null = $names.Add($a) $aliasMap[$a] = $p.Name } } $script:__ParamCache[$CommandName] = [pscustomobject]@{ Names = $names AliasMap = $aliasMap ParamMD = $cmd.Parameters } } $md = $script:__ParamCache[$CommandName] $accepted = $md.Names $aliases = $md.AliasMap $paramMD = $md.ParamMD $prefix = if ([string]::IsNullOrWhiteSpace($LogPrefix)) { "[Build-CallableSplat:$CommandName]" } else { "[$($LogPrefix):$CommandName]" } if ($Log) { Write-Verbose "$prefix building splat (PreferArgs=$PreferArgs IncludeNulls=$IncludeNulls)" } # Gather raw candidates from Config + Arguments (with precedence) $candidates = @{} if ($Config) { foreach ($prop in $Config.PSObject.Properties) { if ($accepted.Contains($prop.Name)) { $candidates[$prop.Name] = $prop.Value } } } if ($Arguments) { foreach ($k in $Arguments.Keys) { if ($accepted.Contains($k)) { if ($PreferArgs -or -not $candidates.ContainsKey($k)) { $candidates[$k] = $Arguments[$k] } } } } $included = New-Object System.Collections.Generic.List[string] $excluded = New-Object System.Collections.Generic.List[string] $reasons = @{} # name -> reason # Normalize, filter, and build the final splat $splat = @{} foreach ($k in $candidates.Keys) { $canonical = $aliases[$k] if (-not $canonical) { $canonical = $k } $v = $candidates[$k] $param = $paramMD[$canonical] $isSwitch = $param -and $param.ParameterType -eq [switch] if ($isSwitch) { if ($v) { $splat[$canonical] = $true $included.Add("$canonical=(switch:$true)") } else { $excluded.Add("$canonical") $reasons[$canonical] = 'switch:false' } continue } if ($IncludeNulls) { $splat[$canonical] = $v $included.Add("$canonical=(value:$([bool]($null -ne $v)))") } else { if ($null -ne $v -and ($v -isnot [string] -or $v -ne '')) { $splat[$canonical] = $v $included.Add("$canonical") } else { $excluded.Add("$canonical") $reasons[$canonical] = 'null-or-empty' } } } if ($Log) { if ($included.Count -gt 0) { Write-Verbose "$prefix included: $(($included -join ', '))" } if ($excluded.Count -gt 0) { $pairs = $excluded | ForEach-Object { $reason = if ($null -ne $reasons[$_]) { $reasons[$_] } else { 'reason:unknown' } "$_ ($reason)" } Write-Verbose "$prefix excluded: $(($pairs -join ', '))" } Write-Verbose "$prefix final count: $($splat.Count)" } return $splat } |