@.ps1
#region Splatter [ 0.5.5 ] : Simple Scripts to Supercharge Splatting (Install-Module Splatter, then Initialize-Splatter -Verb Get,Use,Find ) [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification="This Declares Variables for Other Scripts")] param() ${?@}=${GetSplat}=${gSplat}={ <# .Synopsis Gets a splat .Description Gets a splat for a command .Link Find-Splat .Link Use-Splat .Example @{id=$pid} | Get-Splat .Example @{id=$Pid} | ?@ # ?@ is an alias for Get-Splat .Example @{id=$pid} | & ${?@} # Get-Splat as a script block #> [Alias('?@','gSplat')] param( # The command that is being splatted. [Parameter(Mandatory=$true,Position=0)] [PSObject[]] $Command, # The input object [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=1)] [Alias('InputObject')] [PSObject] $Splat, # If set, will return regardless of if parameters map, are valid, and have enough mandatory parameters [switch] $Force ) begin { # Declare some caches: if (-not ${script:_@p}) { ${script:_@p} = @{} } # * All Parameters if (-not ${script:_@c}) { ${script:_@c} = @{} } # * All commands if (-not ${script:_@mp}) { ${script:_@mp} = @{} } # * All Mandatory Parameters if (-not ${script:_@pp}) { ${script:_@pp} = @{} } # * All Pipelined Parameters $ValidateAttributes = { param( [Parameter(Mandatory)]$value, [Parameter(Mandatory)]$attributes ) foreach ($attr in $attributes) { $_ = $this = $value if ($attr -is [Management.Automation.ValidateScriptAttribute]) { $result = . $attr.ScriptBlock if ($result -ne $true) { $attr } } elseif ($attr -is [Management.Automation.ValidatePatternAttribute] -and (-not [Regex]::new($attr.RegexPattern, $attr.Options, '00:00:05').IsMatch($value)) ) { $attr } elseif ($attr -is [Management.Automation.ValidateSetAttribute] -and $attr.ValidValues -notcontains $value) { $attr } elseif ($attr -is [Management.Automation.ValidateRangeAttribute] -and ( ($value -gt $attr.MaxRange) -or ($value -lt $attr.MinRange) )) {$attr} } } } process { $ap,$ac,$amp = ${script:_@p},${script:_@c}, ${script:_@mp} #region Turn dictionaries into PSObjects if ($Splat -is [Collections.IDictionary]) { $splat = [PSCustomObject]([Ordered]@{} + $Splat) } #endregion Turn dictionaries into PSObjects $in = $Splat foreach ($cmd in $Command) { # Walk over each command $rc = if ($ac.$cmd) { # use cache if available, otherwise: $ac.$cmd } elseif ($cmd -is [string]) { # *find it if it's a [string] $fc = $ExecutionContext.SessionState.InvokeCommand.GetCommand($cmd,'Function,Cmdlet,ExternalScript,Alias') $fc = if ($fc -is [Management.Automation.AliasInfo]) { $fc.ResolvedCommand } else { $fc } $ac.$cmd = $fc $fc } elseif ($cmd -is [ScriptBlock]) { # * Make a temporary command if it's a [ScriptBlock] $hc = $cmd.GetHashCode() $ExecutionContext.SessionState.PSVariable.Set("function:f$hc", $cmd) $c = $ExecutionContext.SessionState.InvokeCommand.GetCommand("f$hc",'Function') $ac.$cmd = $c $c } elseif ($cmd -is [Management.Automation.CommandInfo]) { # * Otherwise, use the command info $ac.$cmd = $cmd $cmd } if (-not $rc) {continue} $cmd = $rc $outSplat,$Invalid,$Unmapped,$paramMap,$Pipe,$NoPipe = foreach ($_ in 1..6){[ordered]@{}} $params = [Collections.ArrayList]::new() $props = @($in.psobject.properties) $pc = $props.Count if (-not ${script:_@pp}.$cmd) { ${script:_@pp}.$cmd = @( foreach ($param in $cmd.Parameters.Values) { foreach ($attr in $param.Attributes) { if ($attr.ValueFromPipeline) { $param } } }) } $cmdMd = $cmd -as [Management.Automation.CommandMetaData] $problems = @( foreach ($vfp in ${script:_@pp}.$cmd) { if ($in -is $vfp.ParameterType -or ($vfp.ParameterType.IsArray -and $in -as $vfp.ParameterType) ) { $v = $in $badAttributes = & $ValidateAttributes $v $vfp.Attributes if ($badAttributes) { @{$vfp.Name=$badAttributes} } if (-not $badAttributes -or $Force) { $null = $params.Add($vfp.Name) $pipe[$vfp.Name] = $v $outSplat[$vfp.Name] = $v $paramMap[$vfp.Name] = $vfp.Name $pipelineParameterSets = @(foreach ($attr in $vfp.Attributes) { if ($attr.ParameterSetName) { $attr.ParameterSetName} }) } } } :NextProperty foreach ($prop in $props) { $cp=$cmd.Parameters $pn = $prop.Name $pv = $prop.Value if (-not $cp) { continue } $param = $cp.$pn if (-not $param) { $k = "${cmd}:$pn" $param = if ($ap[$k]) { $ap[$k] } else { foreach ($p in $cp.Values) { foreach ($a in $p.Aliases) { $ap["${cmd}:$a"] = $p } if ($ap[$k]) { $ap[$k]; break } } } } if (-not $param) { $pn continue } $paramMap[$param.Name] = $pn if ($params -contains $param) { continue } $pt=$param.ParameterType $paramSets = @(foreach ($attr in $param.Attributes) { if ($attr.ParameterSetName) { $attr.ParameterSetName } }) if ($pipelineParameterSets) { $ok = $false foreach ($cmdPs in $paramSets) { $ok = $ok -bor ($pipelineParameterSets -contains $cmdPs) } if (-not $ok -and -not $Force) { continue } } $v = $pv -as $pt if (-not $v -and ($pt -eq [ScriptBlock] -or $pt -eq [ScriptBlock[]])) { $sb = try { foreach ($_ in $pv) { [ScriptBlock]::Create($_) }} catch {$null} if ($sb) { $v = $sb } } if ($null -ne $v) { $nv = try { [PSVariable]::new("$pn", $v, 'Private',$param.Attributes) } catch { @{$pn=$_} } if ($nv -is [PSVariable] -or $Force) { $null = $params.Add($param) :CanItPipe do { foreach ($attr in $param.Attributes) { if ($attr.ValueFromPipeline -or $attr.ValueFromPipelineByPropertyName -and ((-not $pipelineParameterSets) -or ($pipelineParameterSets -contains $attr.ParameterSetName)) ) { $pipe[$prop.Name] = $v break CanItPipe } } $NoPipe[$prop.Name] = $v } while ($false) $outSplat[$prop.Name] = $v } if ($nv -isnot [PSVariable]) { $nv } } else { @{$pn = $param} } }) $Mandatory = @{} foreach ($param in $cmdMd.Parameters.Values) { foreach ($a in $param.Attributes) { if (-not $a.Mandatory) { continue } if ($a -isnot [Management.Automation.ParameterAttribute]) { continue } if (-not $Mandatory[$a.ParameterSetName]) { $Mandatory[$a.ParameterSetName] = [Ordered]@{} } $mp = ($paramMap.($param.Name)) $Mandatory[$a.ParameterSetName].($param.Name) = if ($mp) { if ($pipelineParameterName -contains $param.Name) { $in } else { $outSplat.$mp } } } } $amp.$cmd = $Mandatory $mandatory = $amp.$cmd $missingMandatory = @{} foreach ($m in $Mandatory.GetEnumerator()) { $missingMandatory[$m.Key] = @(foreach ($_ in $m.value.GetEnumerator()) { if ($null -eq $_.Value) { $_.Key } }) } $couldRun = if (-not $Mandatory.Count) { $true } elseif ($missingMandatory.'__AllParameterSets') { $false } else { foreach ($_ in $missingMandatory.GetEnumerator()) { if (-not $_.Value) { $true;break } } } if (-not $couldRun -and -not $Force) { continue } foreach ($p in $problems) { if ($p -is [Hashtable]) { $Invalid += $p } else { $Unmapped[$p] = $in.$p } } if ($Invalid.Count -eq 0) { $Invalid = $null } if ($Unmapped.Count -eq 0) { $Unmapped = $null } $realCmd = if ($cmd -is [Management.Automation.FunctionInfo] -and $cmd.Name.Contains($cmd.ScriptBlock.GetHashCode().ToString())) { $cmd.ScriptBlock } else { $cmd } foreach($_ in ([Ordered]@{ Command = $realCmd CouldRun = $couldRun Invalid = $Invalid Missing = $missingMandatory PercentFit = $(if ($pc) {$outSplat.Count / $pc } else { 0}) Unmapped = $Unmapped PipelineParameter = $Pipe NonPipelineParameter = $NoPipe }).GetEnumerator()) { $outSplat.psobject.properties.Add([Management.Automation.PSNoteProperty]::new($_.Key,$_.Value)) } $outSplat } } } ${.@}=${UseSplat}={ <# .Synopsis Uses a splat. .Description Uses a splat to call a command. If passed from Find-Splat,Get-Splat or Test-Splat, the command will be automatically detected. If called as .@, this will run only provided commands If called as *@, this will run any found commands .Link Get-Splat .Link Find-Splat .Link Test-Splat .Example @{id=$pid} | Use-Splat gps # When calling Use-Splat is globally imported .Example @{id=$pid} | & ${.@} gps # When calling Use-Splat is nested .Example @{LogName='System';InstanceId=43,44}, @{LogName='Application';InstanceId=10000,10005} | .@ Get-EventLog # get a bunch of different log events #> [Alias('.@','uSplat')] param( # One or more commands [Parameter(Position=0)] [PSObject[]] $Command, # Any additional positional arguments that would be passed to the command [Parameter(Position=1,ValueFromRemainingArguments)] [PSObject[]] $ArgumentList = @(), # The splat [Parameter(ValueFromPipeline=$true)] [PSObject[]] $Splat, # If set, will run regardless of if parameters map, are valid, and have enough mandatory parameters. [switch] $Force, # If set, will run the best fit out of multiple commands. # The best fit is the command that will use the most of the input splat. [Alias('BestFit','BestFitFunction', 'BF','BFF')] [switch] $Best, # If set, will stream input into a single pipeline of each command. # The non-pipeable parameters of the first input splat will be used to start the pipeline. # By default, a command will be run once per input splat. [Alias('Pipe')] [switch] $Stream) begin { $pipelines = @{} } process { $WeTrustTheSplat = $false if (-not $Command -and $splat.Length -eq 1 -and $splat[0] -is [Collections.IDictionary] -and $Splat[0].psobject.Properties['Command']) { $Command = $Splat[0].psobject.Properties['Command'].Value $WeTrustTheSplat = $true } elseif (-not $command -and $_ -is [PSObject] -and $_.Command -and $_.Splat) { $WeTrustTheSplat = $true $splat = $_.Splat $command = $_.Command } if ($Best -and $command.Count) { $command = $splat | & ${?@} -Command $command | Sort-Object PercentFit -Descending | Select-Object -ExpandProperty Command -First 1 } if (-not $Command) { Write-Error -Message "No command found" -Category ObjectNotFound -ErrorId 'Use-Splat.CommandNotFound' ;return } #region UseTheSplat foreach ($cmd in $Command) { if ($WeTrustTheSplat) { if ($cmd -is [Management.Automation.CommandInfo] -or $cmd -is [ScriptBlock]) { foreach ($s in $splat) { if ($argumentList) { & $cmd @s @ArgumentList } else { & $cmd @s } } } } else { $Splat | & ${?@} $cmd -Force:$Force | & { process { $i = $_ $np = $i.NonPipelineParameter $c = $_.psobject.properties['Command'].Value if ($Stream) { if (-not $pipelines[$c]) { $stepScript = if ($argumentList) { {& $c @np @argumentList} } else { {& $c @np} } $stepPipeline = $stepScript.GetSteppablePipeline() $pipelines[$c] = $stepPipeline $stepPipeline.Begin($true) } else { $stepPipeline = $pipelines[$c] } $stepPipeline.Process([PSCustomObject]$i.PipelineParameter) return } if ($c -is [Management.Automation.CommandInfo] -or $c -is [ScriptBlock]) { if ($ArgumentList) { & $c @i @ArgumentList } else { & $c @i } } }} } } #endregion UseTheSplat } end { if ($pipelines.Count) { foreach ($v in $pipelines.Values) { $v.End() } } } } ${??@}=${FindSplat}=${fSplat}={ <# .Synopsis Finds commands that can be splatted to given an input. .Description Finds the commands whose input parameters match an input object, and returns an [ordered] dictionary of parameters. .Link Get-Splat .Link Use-Splat .Example @{Id=$pid} | Find-Splat -Global #> [Alias('??@','fSplat')] param( # One or more commands. # If not provided, commands from the current module will be searched. # If there is no current module, all commands will be searched. [Parameter(Position=0)] [string[]]$Command, # The splat [Parameter(ValueFromPipeline=$true,Position=1)] [Alias('InputObject')] [PSObject]$Splat, # If set, will look for all commands, even if Find-Splat is used within a module. [Alias('G')][switch]$Global, # If set, will look for commands within the current module. # To make this work within your own module Install-Splat. [Alias('L')][switch]$Local, # If provided, will look for commands within any number of loaded modules. [Alias('M')][string[]]$Module, # If set, will return regardless of if parameters map, are valid, and have enough mandatory parameters [switch]$Force) begin { $myModule = $MyInvocation.MyCommand.ScriptBlock.Module $cmdTypes = 'Function,Cmdlet,ExternalScript,Alias' $resolveAliases = { begin { $n = 0 } process { $n++ if ($t -is [int] -and $id) { $p = $n* 100 / $t Write-Progress "Resolving" "$_ " -PercentComplete $p -Id $id } if ($_.ResolvedCommand) { $_.ResolvedCommand } else { $_ } } end { Write-Progress 'Resolving Aliases' 'Complete' -Id $id } } $filterCmds = { process { foreach ($c in $Command) { if ($_ -like $c) { return $_ } } } } } process { if (-not $Splat) { return } $id =[Random]::new().Next() $commandList = @(if (-not $Command) { Write-Progress -Id $id -Activity 'Getting Commands' -Status ' ' if ($MyModule -and $Local) { $myModule.ExportedCommands.Values | . $resolveAliases | Select-Object -Unique } elseif ($module) { foreach ($m in $Module) { $rm = Get-Module $m if (-not $rm) { continue } $rm.ExportedCommands.Values | . $resolveAliases | Select-Object -Unique } } else { $allcmds = @($ExecutionContext.SessionState.InvokeCommand.GetCommands('*',$cmdTypes, $true)) $t = $allcmds.Count $allcmds |. $resolveAliases | Select-Object -Unique } } elseif ($module) { foreach ($m in $Module) { $rm = Get-Module $m if (-not $rm) { continue } $rm.ExportedCommands.Values | . $resolveAliases | . $filterCmds | Select-Object -Unique } } elseif ($Global) { foreach ($c in $Command) { $ExecutionContext.SessionState.InvokeCommand.GetCommands($c,$cmdTypes, $true) } } elseif ($MyModule -and $Local) { $myModule.ExportedCommands.Values | . $filterCmds | . $resolveAliases | Select-Object -Unique } else { foreach ($cmd in $Command) { if ($cmd -is [string] -and $cmd.Contains('*')) { $ExecutionContext.SessionState.InvokeCommand.GetCommands($cmd,$cmdTypes, $true) } else { $cmd } } }) $psBoundParameters.Command = $commandList if ($Splat -is [Collections.IDictionary]) { $Splat = if ($splat.GetType().Name -ne 'PSBoundParametersDictionary') { [PSCustomObject]$Splat } else { [PSCustomObject]([Ordered]@{} + $Splat) } } $c,$t= 0, $commandList.Count foreach ($cmd in $commandList) { if ($t -gt 1) { $c++;$p=$c*100/$t Write-Progress -Id $id -Activity 'Finding Splats' -Status "$cmd " -PercentComplete $p } $Splat | & ${?@} $cmd -Force:$Force | Select-Object -Property * -ExcludeProperty SyncRoot,IsSynchronized,IsReadOnly,IsFixedSize,Count | & { process { if ($_.PercentFit -eq 0) { return } $_.pstypenames.clear() $_.pstypenames.add('Find.Splat.Output') $keys, $values = @($_.Keys), @($_.Values) $resplat = [Ordered]@{} for ($i=0;$i -lt $keys.count;$i++) { $resplat[$keys[$i]] = $values[$i] } $_.psobject.properties.remove('Keys') $_.psobject.properties.remove('Values') $_.psobject.properties.Add([Management.Automation.PSNoteProperty]::new('Splat', $resplat)) $_ } } } Write-Progress -Id $id -Completed -Activity 'Finding Splats' -Status 'Complete!' } } #endregion Splatter [ 0.5.5 ] : Simple Scripts to Supercharge Splatting (Install-Module Splatter, then Initialize-Splatter -Verb Get,Use,Find ) |