Merge-Splat.ps1
function Merge-Splat { <# .Synopsis Merges one or more splats .Description Merges one or more hashtables and property bags into one [ordered] hashtable. Allows you to -Remove specific keys from any object Allows you to -Include or -Exclude wildcards of keys (or patterns, with -RegularExpression) Allows you to -Map additional values if a value if found. .Link Get-Splat .Link Use-Splat .Example @{a='b'}, @{c='d'} | Merge-Splat .Example [PSCustomOBject]@{a='b'}, @{c='d'} | Merge-Splat -Add @{e='f'} -Remove c .Example @{id=$pid} | Use-Splat Get-Process | Merge-Splat -Include Name .Example @{n=$(Get-Random) } | Merge-Splat -Map @{ N = { if (-not ($_ % 2)) { @{IsEven=$true;IsOdd=$false} } else { @{IsEven=$false;IsOdd=$true}} } } #> param( # The splat [Parameter(ValueFromPipeline=$true)] [Alias('InputObject')] [PSObject[]] $Splat, # Splats or objects that will be added to the splat. [Parameter(Position=0,ValueFromRemainingArguments=$true)] [Alias('With', 'W', 'A', '+')] [PSObject[]] $Add, # The names of the keys to remove from the splat [Alias('Delete','Drop', 'D','R', '-')] [string[]] $Remove, # Patterns of names to include in the splat. # If provided, only keys that match at least one -Include pattern will be kept. # By default, these are wildcards, unlesss -RegularExpression is passed. [Alias('E', 'EX')] [string[]] $Include, # Patterns of names to exclude from the splat. # By default, these are wildcards, unlesss -RegularExpression is passed. [Alias('I', 'IN')] [string[]] $Exclude, # If set, all patterns matched will be assumed to be RegularExpressions, not wildcards [Alias('Regex','RX')] [switch] $RegularExpression, # A map of new data to add. The key is the name of the original property. # The value can be any a string, a hashtable, or a script block. # If the value is a string, it will be treated as a key, and the original property will be copied to this key. # If the value is a hashtable, it will add the values contained in Map. # If the value is a ScriptBlock, it will combine the output of this script block with the splat. [Alias('M','ReMap')] [Collections.IDictionary] $Map, [switch] $Keep, [switch] $Replace) begin { $accumulate = [Collections.ArrayList]::new() $aSplat = {param($o, $i) if ($i -is [Collections.IDictionary]) { try { $o += $i } catch { foreach ($kv in $i.GetEnumerator()) { $gotIt? = $o.Contains($kv.Key) if ($gotIt? -and $Keep) { continue } if ($gotIt? -and $Replace) { $o[$kv.Key] = $kv.Value;continue } elseif ($gotIt?) { if ($o[$kv.Key] -isnot [Object[]]) { $o[$kv.Key] = @($o[$kv.Key]) } $o[$kv.Key] += $kv.Value } else { $o[$kv.Key] = $kv.Value } } } } else { foreach ($prop in $i.psobject.properties) { $o[$prop.Name] = $prop.Value } } } $imSplat = { if (-not $accumulate.Count) { return } $o = [Ordered]@{} foreach ($in in $accumulate) { . $aSplat $o $in } $ok = @($o.Keys) :nextKey foreach ($k in $ok) { if ($Map -and $map.$k) { foreach ($mk in $map.$k) { if ($mk -is [string]) { $o[$mk] = $o[$k] } elseif ($mk -is [Collections.IDictionary]) { . $aSplat $o $mk } elseif ($mk -is [ScriptBlock]) { $_ = $o[$k] $mkr = . $mk $_ foreach ($r in $mkr) { . $aSplat $o $r } } } } if ($Exclude) { foreach ($ex in $Exclude) { if (($RegularExpression -and ($k -match $ex)) -or ($k -like $ex)) { $o.Remove($k) continue nextKey } } } if ($include) { foreach ($in in $include) { if (($RegularExpression -and ($k -match $in)) -or ($k -like $in)) { continue nextKey } } $o.Remove($k) } } foreach ($r in $Remove) { $o.Remove($r) } $accumulate.Clear() $o } } process { $isTheEndOfTheLine? = $MyInvocation.PipelinePosition -eq $MyInvocation.PipelineLength if ($Splat) { $accumulate.AddRange($Splat) } if ($Add) { $accumulate.AddRange($add) } if (-not $isTheEndOfTheLine?) { . $imSplat } } end { . $imSplat } } |