Out-Splatter.ps1
function Out-Splatter { <# .Synopsis Outputs code that splats .Description Outputs a function or script that primarily calls another command. This can get messy to write by hand. .Link Initialize-Splatter #> [CmdletBinding(DefaultParameterSetName='JustTheSplatter')] param( # The name of the command that will be splatted [Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true)] [Alias('Name')] [string] $CommandName, # A hashtable of default parameters. These will always be passed to the underlying command by name. [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)] [Alias('DefaultParameters')] [Hashtable] $DefaultParameter = @{}, # A list of arguments. These will be always be passed to the underlying commands by position. # Items starting with $ will be treated as a variable. [Parameter(ValueFromPipelineByPropertyName=$true)] [string[]] $ArgumentList, # An optional name of a generated function. # If provided, this function will declare any input parameters specified in -InputParameter [Parameter(Mandatory=$true,Position=2,ValueFromPipelineByPropertyName=$true,ParameterSetName='FunctionalSplatter')] [string] $FunctionName, # A list of parameters names that will be inputted from the original command into the splat. # If generating a function, these parameter declarations will be copied from the underlying command. # Help for these parameters will be included as comment-based help [Parameter(Position=3,ValueFromPipelineByPropertyName=$true)] [Alias('IncludeParameter')] [string[]] $InputParameter, # A list of parameters that will be excluded from the original function. # This is only valid when generating a function. # Wildcards may be used. [Parameter(Position=4,ValueFromPipelineByPropertyName=$true)] [string[]] $ExcludeParameter, # If set, values from input parameters will override default values. [Alias('OverrideDefault','OverwriteDefault', 'DefaultOverwrite')] [Parameter(ValueFromPipelineByPropertyName=$true)] [Switch] $DefaultOverride, # If set, any variable with a non-null value matching the input parameters will be used to splat. # If not set, only bound parameters will be used to splat. # If no function name is provided, this will automatically be set [Parameter(ValueFromPipelineByPropertyName=$true)] [Switch] $VariableInput, # The name of the variable used to hold the splatted parameters. By default, splat [Parameter(ValueFromPipelineByPropertyName=$true)] [string] $SplatName = 'splat', # The synopsis. # This is used to make comment-based help in a generated function. # By default, it is : "Wraps $CommandName" [Parameter(ValueFromPipelineByPropertyName=$true,ParameterSetName='FunctionalSplatter')] [string] $Synopsis, # The description. # This is used to make comment-based help in a generated function. [Parameter(ValueFromPipelineByPropertyName=$true,ParameterSetName='FunctionalSplatter')] [string] $Description, # The CmdletBinding attribute for a new function [Parameter(ValueFromPipelineByPropertyName=$true,ParameterSetName='FunctionalSplatter')] [string] $CmdletBinding, # The serialization depth for default parameters. By default, 2. [uint32] $SerializationDepth = 2, # If set, will cross errors into the output stream. # You SHOULD cross the streams when dealing with console applications, as many of them like to return output on standard error. [Parameter(ValueFromPipelineByPropertyName=$true)] [Switch] $CrossStream, # A script block used to filter the results [ScriptBlock]$Where, # A script to run before the splatter starts [ScriptBlock]$Begin, # A script to run on each splatter result [ScriptBlock]$Process, # A script to run after the splat is over [ScriptBlock]$End, # If provided, will pipe directly into the contents of this script block. # This assumes that the first item in the script block is a command, and it will accept the output of the splat as pipelined input [ScriptBlock] [Alias('PipeInto','Pipe')] $PipeTo, # A set of additional parameter declarations. The keys are the names of the parameters, and the values can be a type and a string containing parameter binding and inline help. [Parameter(ValueFromPipelineByPropertyName=$true,ParameterSetName='FunctionalSplatter')] [Hashtable] $AdditionalParameter) process { $MyParameters = @{} + $PSBoundParameters $commandExists = $ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'All') if (-not $commandExists) { Write-Error "$CommandName does not exist" return } $defaultDeclaration = if ($DefaultParameter.Count) { $toSplat = "@' $(ConvertTo-Json $DefaultParameter -Depth $SerializationDepth) '@" "`$${splatName}Default = ConvertFrom-Json $toSplat" "`$$splatName = @{}" "foreach (`$property in `$${splatName}Default.psobject.properties) { `$$SplatName[`$property.Name] = `$property.Value if (`$property.Value -is [string] -and `$property.Value.StartsWith('`$')) { `$$SplatName[`$property.Name] = `$executionContext.SessionState.PSVariable.Get(`$property.Value.Substring(1)).Value } }" } elseif ($ArgumentList.Count) { "`$$splatName = " + @(foreach ($a in $ArgumentList) { if ($a.StartsWith('$')) { $a } else { "'$($a.Replace("'","''"))'" } }) -join ',' } else { "`$$splatName = @{}" } if ($InputParameter.Count -eq 1 -and $InputParameter -eq '*') { if ($commandExists -is [Management.Automation.ApplicationInfo]) { $InputParameter = @() } else { $InputParameter = ($commandExists -as [Management.Automation.CommandMetaData]).Parameters.Keys } } if (-not $FunctionName) { $VariableInput = $true } $paramSplat = if ($InputParameter) { if ($VariableInput) { " #region Copy Parameters from $CommandName foreach (`$in in '$($inputParameter -join "','")') { `$var = `$executionContext.SessionState.PSVariable.Get(`$in) if (-not `$var) { continue } $(if ($DefaultOverride) { "`$$splatName.`$in = `$var.Value" } else { "`$$splatName.`$in = `$var.Value" }) } #endregion Copy Parameters from $CommandName " } else { "#region Copy Parameters from $CommandName `$MyParameters = @{} + `$psBoundParameters # Copy `$PSBoundParameters foreach (`$in in '$($inputParameter -join "','")') { $(if ($DefaultOverride) { "if (`$myParameters.`$in) { `$$splatName.`$in = `$myParameters.`$in }" } else { "if (-not `$$splatName.`$in -and `$myParameters.`$in) { `$$splatName.`$in = `$myParameters.`$in }"}) } #endregion Copy Parameters from $CommandName " } } else { @() } $paramSplat = $paramSplat -join ([Environment]::NewLine) $cmdDef = if ([Management.Automation.ExternalScriptInfo],[Management.Automation.ApplicationInfo] -contains $commandExists.GetType()) { " & '$($commandExists.Source.Replace("'","''"))' @$splatName" } else { "$commandExists @$splatName" } if ($CrossStream) { $cmdDef += " 2>&1" } if ($Where) { $cmdDef += " | Where-Object {$Where}" } if ($PipeTo) { $cmdDef += "| $PipeTo" } if ($Begin -or $Process -or $End) { $cmdDef += " | Foreach-Object$(if ($Begin) { " -Begin { $Begin }"})$(if ($Process) { " -Process { $Process }"})$(if ($End) { " -End { $end }"})" } $coreSplat = (@() + $defaultDeclaration + $paramSplat + $cmdDef) -join ([Environment]::NewLine) if ($FunctionName) { $commandMeta = $commandExists -as [Management.Automation.CommandMetadata] foreach ($k in @($commandMeta.Parameters.Keys)) { if ($InputParameter -notcontains $k) { $null =$commandMeta.Parameters.Remove($k) } } $originalCmdletBinding = [Management.Automation.Proxycommand]::GetCmdletBindingAttribute($commandExists) $cmdHelp = Get-Help -Name $commandExists if ($cmdHelp -is [string]) { $cmdHelp = $null } $paramBlock = [Management.Automation.Proxycommand]::GetParamBlock($commandExists) -replace '\{(\S{1,})\}', '$1' $paramParts = $paramBlock -split ',\W{1,}[\[$]' -ne '' $paramBlockParts = @(foreach ($param in $paramParts) { $lastDollar = $param.LastIndexOf('$') $parameterName = $param.Substring($lastDollar + 1).Trim() $parameterHelp = if ($cmdHelp) { $cmdHelp.parameters[0].parameter | Where-Object { $_.Name -eq $parameterName -and $_.Description }| Select-Object -ExpandProperty Description | Select-Object -ExpandProperty Text } else { $null } $param = $param.Trim() if (-not $param.StartsWith('$') -and -not $param.StartsWith('[')) { $param = "[$param" } if ($ExcludeParameter) { $shouldExclude = foreach ($ex in $ExcludeParameter) { if ($parameterName -like "$ex") { $true break } } if ($shouldExclude) { continue } } if ($parameterHelp) { $lines = "$parameterHelp".Split("`r`n",[StringSplitOptions]'RemoveEmptyEntries') if ($lines.Count -lt 8) { (@(foreach ($l in $lines) { " #$l" }) -join ([Environment]::NewLine)) + ([Environment]::NewLine) + (' '*4) + $param } else { " <# $parameterHelp #> $param" } } else { $param } }) if ($AdditionalParameter) { foreach ($kv in $AdditionalParameter.GetEnumerator()) { $varName = if ($kv.Key.StartsWith('$')) { $kv.Key } else { '$' + $kv.Key } $newParamBlockPart = if ($kv.Value -is [type]) { if ($kv.Value.FullName -like "System.*") { " [$($kv.Value.Fullname.Substring(7))]$varName" } elseif ($kv.Value -eq [switch]) { " [switch]$varName" } else { " [$($kv.Value.Fullname)]$varName" } } elseif ($kv.Value -is [string]) { $varDeclared = $false $newLines = foreach ($line in $kv.Value -split [Environment]::NewLine) { $trimLine = $Line.Trim() if ($trimLine.StartsWith('[')) { if ($trimLine -like $varName) { $varDeclared = $true } (' ' * 4) + $trimLine } # It's an attribute! elseif ($trimLine.StartsWith('$')) { # It's a variable! $varDeclared = $true (' ' * 4) + $trimLine } elseif ($trimLine.StartsWith('#')) { # It's a comment! (' ' * 4) + $trimLine } else { # Otherwise, we'll treat it like a comment anyways (' ' * 4) + '#' + $trimLine } } if (-not $varDeclared) { $newLines += (' ' * 4) + $varName } $newLines -join ([Environment]::NewLine) } elseif ($kv.Value -is [Object[]]) { } if ($newParamBlockPart) { $paramBlockParts += $newParamBlockPart } } } $paramBlock = $paramBlockParts -join (',' + ([Environment]::NewLine * 2)) if (-not $Synopsis) { $Synopsis = "Wraps $CommandName" } if (-not $Description) { $Description = "Calls $CommandName, using splatting" } "function $FunctionName { <# .Synopsis $Synopsis .Description $Description .Link $CommandName #>$(if ($CmdletBinding) { if ($CmdletBinding -like "*CmdletBinding*") { [Environment]::NewLine + (' '*4) + $CmdletBinding } else { [Environment]::NewLine + (' '*4) + "[CmdletBinding($CmdletBinding)]" } } elseif ($originalCmdletBinding) { [Environment]::NewLine + (' '*4) + $originalCmdletBinding }) param( $paramBlock) process { $(@(foreach ($line in $coreSplat -split ([Environment]::Newline)) { if ($line.StartsWith("'@")) { $line } else { (' ' * 8) + $line } }) -join ([Environment]::NewLine)) } } " #[Management.Automation.Proxycommand]::GetHelpComments($cmdHelp) } else { $coreSplat } } } |