Commands/PipeScript/New-PipeScript.ps1
function New-PipeScript { <# .Synopsis Creates new PipeScript. .Description Creates new PipeScript and PowerShell ScriptBlocks. This allow you to create scripts dynamically. .EXAMPLE # Without any parameters, this will make an empty script block New-PipeScript # Should -BeOfType([ScriptBlock]) .EXAMPLE # We can use -AutoParameter to automatically populate parameters: New-PipeScript -ScriptBlock { $x + $y} -AutoParameter .EXAMPLE # We can use -AutoParameter and -AutoParameterType to automatically make all parameters a specific type: New-PipeScript -ScriptBlock { $x, $y } -AutoParameter -AutoParameterType double .EXAMPLE # We can provide a -FunctionName to make a function. # New-PipeScript transpiles the scripts it generates, so this will also declare the function. New-PipeScript -ScriptBlock { Get-Random -Min 1 -Max 20 } -FunctionName ANumberBetweenOneAndTwenty ANumberBetweenOneAndTwenty # Should -BeLessThan 21 .EXAMPLE # We can provide parameters as a dictionary. New-PipeScript -Parameter @{"foo"=@{ Name = "foo" Help = 'Foobar' Attributes = "Mandatory","ValueFromPipelineByPropertyName" Aliases = "fubar" Type = "string" }} .EXAMPLE # We can provide parameters from .NET reflection. # We can provide additional parameter help with -ParameterHelp New-PipeScript -Parameter ([Net.HttpWebRequest].GetProperties()) -ParameterHelp @{ Accept=' HTTP Accept. HTTP Accept indicates what content types the web request will accept as a response. ' } .EXAMPLE # If a .NET type has XML Documentation, this can generate parameter help. New-PipeScript -FunctionName New-TableControl -Parameter ( [Management.Automation.TableControl].GetProperties() ) -Process { New-Object Management.Automation.TableControl -Property $psBoundParameters } -Synopsis 'Creates a table control' Get-Help New-TableControl -Parameter * .EXAMPLE $CreatedCommands = [Management.Automation.TableControl], [Management.Automation.TableControlColumnHeader], [Management.Automation.TableControlRow], [Management.Automation.TableControlColumn], [Management.Automation.DisplayEntry] | New-PipeScript -Noun { $_.Name } -Verb New -Alias { "Get-$($_.Name)", "Set-$($_.Name)" } -Synopsis { "Creates, Changes, or Gets $($_.Name)" } New-TableControl -Headers @( New-TableControlColumnHeader -Label "First" -Alignment Left -Width 10 New-TableControlColumnHeader -Label "Second" -Alignment Center -Width 20 New-TableControlColumnHeader -Label "Third" -Alignment Right -Width 20 ) -Rows @( New-TableControlRow -Columns @( New-TableControlColumn -DisplayEntry ( New-DisplayEntry First Property ) New-TableControlColumn -DisplayEntry ( New-DisplayEntry Second Property ) New-TableControlColumn -DisplayEntry ( New-DisplayEntry Third Property ) ) ) #> [Alias('New-ScriptBlock')] [CmdletBinding(PositionalBinding=$false)] param( # An input object. # This can be anything, and in a few special cases, this can become the script. # If the InputObject is a `[ScriptBlock]`, this will be treated as if it was the -Process parameter. # If the InputObject is a `[Type]`, this will create a script to work with that type. [Parameter(ValueFromPipeline)] $InputObject, # Defines one or more parameters for a ScriptBlock. # Parameters can be defined in a few ways: # * As a ```[Collections.Dictionary]``` of Parameters # * As the ```[string]``` name of an untyped parameter. # * As a ```[ScriptBlock]``` containing only parameters. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Parameters','Property','Properties')] $Parameter, # If provided, will output to this path instead of returning a new script block. [Parameter(ValueFromPipelineByPropertyName)] [string] $OutputPath, # The dynamic parameter block. [Parameter(ValueFromPipelineByPropertyName)] [ValidateScript({ if ($_ -isnot [ScriptBlock]) { return $true } if ($_.Ast.DynamicParamBlock -or $_.Ast.BeginBlock -or $_.Ast.ProcessBlock) { throw "ScriptBlock should not have any named blocks" } return $true })] [ValidateScript({ if ($_ -isnot [ScriptBlock]) { return $true } if ($_.Ast.ParamBlock.Parameters.Count) { throw "ScriptBlock should not have parameters" } return $true })] [Alias('DynamicParameterBlock')] [ScriptBlock] $DynamicParameter, # The begin block. [Parameter(ValueFromPipelineByPropertyName)] [ValidateScript({ if ($_ -isnot [ScriptBlock]) { return $true } if ($_.Ast.DynamicParamBlock -or $_.Ast.BeginBlock -or $_.Ast.ProcessBlock) { throw "ScriptBlock should not have any named blocks" } return $true })] [ValidateScript({ if ($_ -isnot [ScriptBlock]) { return $true } if ($_.Ast.ParamBlock.Parameters.Count) { throw "ScriptBlock should not have parameters" } return $true })] [Alias('BeginBlock')] [ScriptBlock] $Begin, # The process block. # If a [ScriptBlock] is piped in and this has not been provided, # -Process will be mapped to that script. [Parameter(ValueFromPipelineByPropertyName)] [Alias('ProcessBlock','ScriptBlock')] [ScriptBlock] $Process, # The end block. [Parameter(ValueFromPipelineByPropertyName)] [ValidateScript({ if ($_ -isnot [ScriptBlock]) { return $true } if ($_.Ast.DynamicParamBlock -or $_.Ast.BeginBlock -or $_.Ast.ProcessBlock) { throw "ScriptBlock should not have any named blocks" } return $true })] [ValidateScript({ if ($_ -isnot [ScriptBlock]) { return $true } if ($_.Ast.ParamBlock.Parameters.Count) { throw "ScriptBlock should not have parameters" } return $true })] [Alias('EndBlock')] [ScriptBlock] $End, # The script header. [Parameter(ValueFromPipelineByPropertyName)] [string] $Header, # If provided, will automatically create parameters. # Parameters will be automatically created for any unassigned variables. [Parameter(ValueFromPipelineByPropertyName)] [Alias('AutoParameterize','AutoParameters')] [switch] $AutoParameter, # The type used for automatically generated parameters. # By default, ```[PSObject]```. [Parameter(ValueFromPipelineByPropertyName)] [type] $AutoParameterType = [PSObject], # If provided, will add inline help to parameters. [Parameter(ValueFromPipelineByPropertyName)] [Collections.IDictionary] $ParameterHelp, <# If set, will weakly type parameters generated by reflection. 1. Any parameter type implements IList should be made a [PSObject[]] 2. Any parameter that implements IDictionary should be made an [Collections.IDictionary] 3. Booleans should be made into [switch]es 4. All other parameter types should be [PSObject] #> [Parameter(ValueFromPipelineByPropertyName)] [Alias('WeakType', 'WeakParameters', 'WeaklyTypedParameters', 'WeakProperties', 'WeaklyTypedProperties')] [switch] $WeaklyTyped, # The name of the function to create. [Parameter(ValueFromPipelineByPropertyName)] [string] $FunctionName, # The verb of the function to create. This implies a -FunctionName. [Parameter(ValueFromPipelineByPropertyName)] [string] $Verb, # The noun of the function to create. This implies a -FunctionName. [Parameter(ValueFromPipelineByPropertyName)] [string] $Noun, # The type or namespace the function to create. This will be ignored if -FunctionName is not passed. # If the function type is not function or filter, it will be treated as a function namespace. [Alias('FunctionNamespace','CommandNamespace')] [string] $FunctionType = 'function', # A description of the script's functionality. If provided with -Synopsis, will generate help. [Parameter(ValueFromPipelineByPropertyName)] [string] $Description, # A short synopsis of the script's functionality. If provided with -Description, will generate help. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Summary')] [string] $Synopsis, # A list of examples to use in help. Will be ignored if -Synopsis and -Description are not passed. [Alias('Examples')] [string[]] $Example, # A list of links to use in help. Will be ignored if -Synopsis and -Description are not passed. [Alias('Links')] [string[]] $Link, # A list of attributes to declare on the scriptblock. [string[]] $Attribute, # A list of potential aliases for the command. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Aliases')] [string[]] $Alias, # If set, will try not to create mandatory parameters. [switch] $NoMandatory, # If set, will not transpile the created code. [switch] $NoTranspile, # A Reference Object. # This can be used for properties that are provided from a JSON Schema or OpenAPI definition or some similar structure. # It will take a slash based path to a component or property and use that as it's value. $ReferenceObject ) begin { $ParametersToCreate = [Ordered]@{} $parameterScriptBlocks = @() $allDynamicParameters = @() $allBeginBlocks = @() $allEndBlocks = @() $allProcessBlocks = @() $allHeaders = @() ${?<EmptyParameterBlock>} = '^[\s\r\n]{0,}param\(' -replace '\)[\s\r\n]{0,}$' filter embedParameterHelp { if ($_ -notmatch '^\s\<\#' -and $_ -notmatch '^\s\#') { $commentLines = @($_ -split '(?>\r\n|\n)') if ($commentLines.Count -gt 1) { '<#' + [Environment]::NewLine + "$_".Trim() + [Environment]::newLine + '#>' } else { "# $_" } } else { $_ } } filter psuedoTypeToRealType { switch ($_) { string { [string] } integer { [int] } number { [double]} boolean { [switch] } array { [PSObject[]] } object { [PSObject] } default { if ($_ -as [type]) { $_ -as [type] } } } } filter oneOfTheseProperties { foreach ($arg in $args) { if ($_.$arg) { return $_.$arg } } } # Default 'NoEnd' to false. # We'll check this at the beginning of the end to make sure we need to run the code in the end block. $NoEnd = $false } process { $myParameters = [Ordered]@{} + $psBoundParameters # If the input was a dictionary if ($InputObject -is [Collections.IDictionary]) { # make it a custom object. $InputObject = [PSCustomObject]$InputObject $myCmd = $MyInvocation.MyCommand # Since we're too late to do a `[ValueFromPipelineByPropertyName]` binding to the parameters, # do it ourselves by walking over each parameter. :nextParameter foreach ($param in $myCmd.Parameters.Values) { if ($null -ne $inputObject.($param.Name)) { # If the -InputObject contains a parameter name # bind it by setting the variable $ExecutionContext.SessionState.PSVariable.Set($param.Name, $inputObject.($param.Name)) } else { # Otherwise, check each of the aliases for this parameter foreach ($paramAlias in $param.Aliases) { # and if the -InputObject has that alias if ($null -ne $inputObject.$paramAlias) { # bind it by setting the variable. $ExecutionContext.SessionState.PSVariable.Set($param.Name, $inputObject.$paramAlias) continue nextParameter } } } } } # If the inputobject is a [Type] if ($InputObject -is [Type]) { if (-not $myParameters['Parameter']) { $myParameters['Parameter'] = $parameter = @( $InputObject.GetProperties() @{ "InputObject"= @{ Name="InputObject" Attribute='ValueFromPipeline' Type=$InputObject } "ArgumentList" = @{ Name="ArgumentList" Attribute="ValueFromRemainingArguments" Type=[PSObject[]] } "PassThru" = @{ Name = 'PassThru' Type=[switch] } } ) } # If we didn't supply any script, make one. if (-not ($myParameters['Process'] -or $myParameters['Begin'] -or $myParameters['End'] -or $myParameters['DynamicParameter'] )) { $myParameters['Attribute'] = '[CmdletBinding(PositionalBinding=$false)]' $begin = $myParameters['Begin'] = [scriptblock]::create( "`$FunctionBaseType = [$($InputObject.FullName -replace '^\.System')]" ) $Process = $myParameters['Process'] = { $invocationName = $MyInvocation.InvocationName if ($invocationName -match '^(?>New|Add)') { if ($FunctionBaseType::new.OverloadDefinitions -like '*()') { $InputObject = $FunctionBaseType::new() } elseif ($ArgumentList) { $InputObject = $FunctionBaseType::new.Invoke($ArgumentList) } $invocationName = 'Set' $PassThru = $true } switch -regex ($invocationName) { "^Set" { # If we're setting, foreach ($in in $InputObject) { foreach ($paramName in ([Management.Automation.CommandMetaData]$MyInvocation.MyCommand).Parameters.Keys) { # change any property on that object if ( $paramName -ne 'InputObject' -and $myParameters.Contains($paramName) -and $in.GetType().GetProperty($paramName) ) { $in.$paramName = $myParameters[$paramName] } } if ($PassThru) { $in } } } default { foreach ($variable in Get-Variable) { if ($variable.Value -is $FunctionBaseType) { $variable.Value } } } } } } # Here's a slight rub: # We'd like to be able to pipe multiple types in. # If that's the case, we want to return/declare multiple functions # So by calling ourselves recursively here, we can do that without moving all of the code from the end block $myParameters.Remove('InputObject') New-PipeScript @myParameters $noEnd = $true return } if ($Synopsis) { if (-not $Description) { $Description = $Synopsis } function indentHelpLine { foreach ($line in $args -split '(?>\r\n|\n)') { (' ' * 4) + $line.TrimEnd() } } $helpHeader = @( "<#" ".Synopsis" indentHelpLine $Synopsis ".Description" indentHelpLine $Description foreach ($helpExample in $Example) { ".Example" indentHelpLine $helpExample } foreach ($helplink in $Link) { ".Link" indentHelpLine $helplink } "#>" ) -join [Environment]::Newline $allHeaders += $helpHeader } if ($Alias) { $allHeaders += "[Alias('$($alias -replace "'","''" -join "','")')]" } if ($Attribute) { $allHeaders += $Attribute } # If -Parameter was passed, we will need to define parameters. if ($parameter) { # We may also effectively want to recurse thru these values # (that is, a parameter can imply the existence of other parameters) # So, instead of going thru a normal list, we're putting everything into a queue $parameterQueue = [Collections.Queue]::new(@($parameter)) $parameterList = [Collections.Generic.List[PSObject]]::new() foreach ($param in $parameter) { $null = $parameterList.Add($param) } while ($parameterList.Count) { $param = $parameterList[0] $parameterList.RemoveAt(0) # this will end up populating an [Ordered] dictionary, $parametersToCreate. # However, for ease of use, -Parameter can be very flexible. # The -Parameter can be a dictionary of parameters. if ($Param -is [Collections.IDictionary]) { $parameterType = '' # If it is, walk thur each parameter in the dictionary foreach ($EachParameter in $Param.GetEnumerator()) { # Continue past any parameters we already have if ($ParametersToCreate.Contains($EachParameter.Key)) { continue } # If the parameter is a string and the value is not a variable if ($EachParameter.Value -is [string] -and $EachParameter.Value -notlike '*$*') { $parameterName = $EachParameter.Key $ParametersToCreate[$EachParameter.Key] = @( if ($parameterHelp -and $parameterHelp[$eachParameter.Key]) { $parameterHelp[$eachParameter.Key] | embedParameterHelp } $parameterAttribute = "[Parameter(ValueFromPipelineByPropertyName)]" $parameterType '$' + $parameterName ) -ne '' } # If the value is a string and the value contains variables elseif ($EachParameter.Value -is [string]) { # embed it directly. $ParametersToCreate[$EachParameter.Key] = $EachParameter.Value } # If the value is a ScriptBlock elseif ($EachParameter.Value -is [ScriptBlock]) { # Embed it $ParametersToCreate[$EachParameter.Key] = # If there was a param block on the script block if ($EachParameter.Value.Ast.ParamBlock) { # embed the parameter block (except for the param keyword) $EachParameter.Value.Ast.ParamBlock.Extent.ToString() -replace ${?<EmptyParameterBlock>} } else { # Otherwise '[Parameter(ValueFromPipelineByPropertyName)]' + ( $EachParameter.Value.ToString() -replace "\`$$($eachParameter.Key)[\s\r\n]$" -replace # Replace any trailing variables ${?<EmptyParameterBlock>} # then replace any empty param blocks. ) } } # If the value was an array elseif ($EachParameter.Value -is [Object[]]) { $ParametersToCreate[$EachParameter.Key] = # join it's elements by newlines $EachParameter.Value -join [Environment]::Newline } elseif ($EachParameter.Value -is [Collections.IDictionary] -or $EachParameter.Value -is [PSObject]) { # This is the "easy" way to specify a lot of advanced properties: # A hashtable of hashtables/PSObjects, where the keys are made as convenient and exhaustive as possible: $parameterMetadata = $EachParameter.Value $parameterName = $EachParameter.Key if ($parameterMetadata.Name) { $parameterName = $parameterMetadata.Name } $parameterAttributeParts = @() $ParameterOtherAttributes = @() # Attributes can be in .Attribute/.Attributes $attrs = @($parameterMetadata | oneOfTheseProperties Attribute Attributes) # Aliases can be in .Alias/.Aliases [string[]]$aliases = @($parameterMetadata | oneOfTheseProperties Alias Aliases) # Help can be in .Help/.Description/.Synopsis/.Summary [string]$parameterHelpText = $parameterMetadata | oneOfTheseProperties Help Description Synopsis Summary # Bindings can be in .Binding/.Bindings/.DefaultBinding/.DefaultBindingProperty. $Bindings = @( $parameterMetadata | oneOfTheseProperties Binding Bindings DefaultBinding DefaultBindingProperty ) # Ambient values can be in .AmbientValue/.CoerceValue $AmbientValue = @( $parameterMetadata | oneOfTheseProperties AmbientValue CoerceValue ) # Metadata can be in .Metadata/.ReflectionMetadata/.ParameterMetadata $parameterMetadataProperties = @( $parameterMetadata | oneOfTheseProperties Metadata ReflectionMetadata ParameterMetadata ) # Valid Values can be found in .ValidValue/.ValidValues/.ValidateSet $parameterValidValues = @( $parameterMetadata | oneOfTheseProperties ValidValue ValidValues ValidateSet ) $parameterValidPattern = @( $parameterMetadata | oneOfTheseProperties ValidatePattern Pattern Regex ) # Default values can be found in .DefaultValue/.Default $parameterDefaultValue = @( $parameterMetadata | oneOfTheseProperties DefaultValue Default ) $aliasAttribute = @(foreach ($aka in $aliases) { $aka -replace "'","''" }) -join "','" if ($aliasAttribute) { $aliasAttribute = "[Alias('$aliasAttribute')]" } if ($parameterValidValues) { $attrs += "[ValidateSet('$($parameterValidValues -replace "'","''" -join "','")')]" } if ($parameterValidPattern) { $attrs += "[ValidatePattern('$($parameterValidPattern -replace "'","''")')]" } if ($Bindings) { foreach ($bindingProperty in $Bindings) { $attrs += "[ComponentModel.DefaultBindingProperty('$bindingProperty')]" } } if ($parameterMetadataProperties) { foreach ($pmdProp in $parameterMetadataProperties) { if ($pmdProp -is [Collections.IDictionary]) { foreach ($mdKv in $pmdProp.GetEnumerator()) { $attrs += "[Reflection.AssemblyMetadata('$($mdKv.Key)', '$($mdKv.Value -replace "',''")')]" } } } } if ($AmbientValue) { foreach ($ambient in $AmbientValue) { if ($ambient -is [scriptblock]) { $attrs += "[ComponentModel.AmbientValue({$ambient})]" } } } foreach ($attr in $attrs) { if ($attr -notmatch '^\[') { $parameterAttributeParts += $attr } else { $ParameterOtherAttributes += $attr } } if ( ($parameterMetadata.Mandatory -or $parameterMetadata.required) -and ($parameterAttributeParts -notmatch 'Mandatory') -and -not $NoMandatory) { $parameterAttributeParts = @('Mandatory') + $parameterAttributeParts } $parameterType = $parameterMetadata | oneOfTheseProperties Type ParameterType $ParametersToCreate[$parameterName] = @( if ($ParameterHelpText) { $ParameterHelpText | embedParameterHelp } if ($parameterAttributeParts) { "[Parameter($($parameterAttributeParts -join ','))]" } if ($aliasAttribute) { $aliasAttribute } if ($ParameterOtherAttributes) { $ParameterOtherAttributes } $PsuedoType = $parameterType | psuedoTypeToRealType if ($PsuedoType) { $parameterType = $PsuedoType if ($parameterType -eq [bool]) { "[switch]" } elseif ($parameterType -eq [array]) { "[PSObject[]]" } else { if ($WeaklyTyped) { if ($parameterType.GetInterface -and $parameterType.GetInterface([Collections.IDictionary])) { "[Collections.IDictionary]" } elseif ($parameterType.GetInterface -and $parameterType.GetInterface([Collections.IList])) { "[PSObject[]]" } else { "[PSObject]" } } else { if ($parameterType.IsGenericType -and $parameterType.GetInterface -and $parameterType.GetInterface(([Collections.IList])) -and $parameterType.GenericTypeArguments.Count -eq 1 ) { "[$($parameterType.GenericTypeArguments[0].Fullname -replace '^System\.')[]]" } else { "[$($parameterType.FullName -replace '^System\.')]" } } } } elseif ($parameterType) { "[PSTypeName('$($parameterType -replace '^System\.')')]" } '$' + ($parameterName -replace '^$') + $( if ($parameterDefaultValue) { if ($parameterDefaultValue -is [scriptblock]) { if ($parameterType -eq [scriptblock]) { "= {$ParameterDefaultValue}" } else { "= `$($ParameterDefaultValue)" } } elseif ($parameterDefaultValue -is [string]) { "= `$('$($parameterDefaultValue -replace "'","''")')" } elseif ($parameterDefaultValue -is [bool] -or $parameterDefaultValue -is [switch]) { "= `$$($parameterDefaultValue -as [bool])" } } ) ) -join [Environment]::newLine } } } # If the parameter was a string elseif ($Param -is [string]) { # treat it as parameter name $ParametersToCreate[$Param] = @( if ($parameterHelp -and $parameterHelp[$Param]) { $parameterHelp[$Param] | embedParameterHelp } "[Parameter(ValueFromPipelineByPropertyName)]" "`$$Param" ) -join [Environment]::NewLine } # If the parameter is a [ScriptBlock] elseif ($Param -is [scriptblock]) { # add it to a list of parameter script blocks. $parameterScriptBlocks += if ($Param.Ast.ParamBlock) { $Param } } elseif ($param -is [Management.Automation.CommandInfo] -or $param -is [Management.Automation.CommandMetaData]) { if ($param -isnot [Management.Automation.CommandMetaData]) { $param = $param -as [Management.Automation.CommandMetaData] } if ($param) { $proxyParamBlock = [Management.Automation.ProxyCommand]::GetParamBlock($param) $proxyParamBlock = $proxyParamBlock -replace '\$\{(?<N>\w+)\}','$$${N}' if ($NoMandatory) { $proxyParamBlock = $proxyParamBlock -replace 'Mandatory=\$true', 'Mandatory=$$false' } $parameterScriptBlocks += [scriptblock]::Create(("param(" + $proxyParamBlock + ")")) } } # If the -Parameter was provided via reflection elseif ($Param -is [Reflection.PropertyInfo] -or $Param -as [Reflection.PropertyInfo[]] -or $Param -is [Reflection.ParameterInfo] -or $Param -as [Reflection.ParameterInfo[]] -or $Param -is [Reflection.MethodInfo] -or $Param -as [Reflection.MethodInfo[]] ) { # Find an XML documentation file for the type, if available if (-not $Script:FoundXmlDocsForAssembly) { $Script:FoundXmlDocsForAssembly = @{} } if (-not $Script:FoundXmlDocsForType) { $Script:FoundXmlDocsForType = @{} } $declaringType = $param.DeclaringType $declaringAssembly = $param.DeclaringType.Assembly if (-not $Script:FoundXmlDocsForAssembly[$declaringAssembly]) { $likelyXmlLocation = $declaringAssembly.Location -replace '\.dll$', '.xml' if (Test-Path $likelyXmlLocation) { $Script:FoundXmlDocsForAssembly[$declaringAssembly] = [IO.File]::ReadAllText($likelyXmlLocation) -as [xml] } } if ($Script:FoundXmlDocsForAssembly[$declaringAssembly] -and -not $Script:FoundXmlDocsForType[$declaringType]) { $Script:FoundXmlDocsForType[$declaringType] = foreach ($node in $Script:FoundXmlDocsForAssembly[$declaringAssembly].SelectNodes("//member")) { if ($node.Name -like "*$($declaringType.FullName)*") { $node } } } # check to see if it's a method if ($Param -is [Reflection.MethodInfo] -or $Param -as [Reflection.MethodInfo[]]) { $Param = @(foreach ($methodInfo in $Param) { $methodInfo.GetParameters() # if so, reflect the method's parameters }) } # Walk over each parameter and turn it into a dictionary of dictionaries $propertiesToParameters = [Ordered]@{} foreach ($prop in $Param) { # If it is a property info that cannot be written, skip. if ($prop -is [Reflection.PropertyInfo] -and -not $prop.CanWrite) { continue } # Determine the reflected parameter type. $paramType = if ($prop.ParameterType) { $prop.ParameterType } elseif ($prop.PropertyType) { $prop.PropertyType } else { [PSObject] } $NewParamName = $prop.Name $NewParameterInfo = [Ordered]@{ Name = $NewParamName Attribute = @('ValueFromPipelineByPropertyName') ParameterType = $paramType } if ($ParameterHelp -and $ParameterHelp[$prop.Name]) { $NewParameterInfo.Help = $ParameterHelp[$prop.Name] } elseif ($Script:FoundXmlDocsForType[$declaringType]) { $lookingForString = @("$prop" -split ' ')[-1] $foundXmlDocsForProp = foreach ($docXmlNode in $Script:FoundXmlDocsForType[$declaringType]) { if ($docXmlNode.Name.EndsWith($lookingForString)) { $docXmlNode break } } if ($foundXmlDocsForProp -and $foundXmlDocsForProp.Summary -is [string]) { $NewParameterInfo.Help = $foundXmlDocsForProp.Summary $null = $null } } $propertiesToParameters[$NewParamName] = $NewParameterInfo } $parameterList.Insert(0,$propertiesToParameters) } elseif ($Param -is [PSObject]) { $paramIsReference = $param.'$ref' if ($paramIsReference -and $ReferenceObject) { $ptr = $ReferenceObject $objectPath = $paramIsReference -replace '^#' -replace '^/' -split '[/\.]' -ne '' foreach ($op in $objectPath) { $ptr = $ptr.$op } if ($ptr) { $parameterList.Insert(0,$ptr) } continue } # If there's a parameter name and schema, we can turn this into something useful. if ($param.Name -and $param.schema) { $newParameterInfo = [Ordered]@{name=$param.Name} if ($param.description) { $newParameterInfo.Description = $param.Description } if ($param.schema.type) { $newParameterInfo.ParameterType = $param.schema.type | psuedoTypeToRealType } if (($param.required -or $param.Mandatory) -and -not $NoMandatory) { $newParameterInfo.Mandatory = $true } $parameterList.Insert(0, [Ordered]@{$param.Name=$newParameterInfo}) } } } } # If there is header content, if ($header) { $allHeaders += $Header } # dynamic parameters, if ($DynamicParameter) { $allDynamicParameters += $DynamicParameter } # begin, if ($Begin) { $allBeginBlocks += $begin } if ($InputObject -is [scriptblock] -and -not $myParameters['Process']) { $process = $InputObject } # process, if ($process) { if ($process.BeginBlock -or $process.ProcessBlock -or $process.DynamicParameterBlock -or $Process.ParamBlock) { if ($process.DynamicParameterBlock) { $allDynamicParameters += $process.DynamicParameterBlock } if ($process.ParamBlock) { $parameterScriptBlocks += $Process } if ($Process.BeginBlock) { $allBeginBlocks += $Process.BeginBlock -replace ${?<EmptyParameterBlock>} } if ($process.ProcessBlock) { $allProcessBlocks += $process.ProcessBlock } if ($process.EndBlock) { $allEndBlocks += $Process.EndBlock -replace ${?<EmptyParameterBlock>} } } else { $allProcessBlocks += $process -replace ${?<EmptyParameterBlock>} } } # or end blocks. if ($end) { # accumulate them. $allEndBlocks += $end } # If -AutoParameter was passed if ($AutoParameter) { # Find all of the variable expressions within -Begin, -Process, and -End $variableDefinitions = $Begin, $Process, $End | Where-Object { $_ } | Search-PipeScript -AstType VariableExpressionAST | Select-Object -ExpandProperty Result foreach ($var in $variableDefinitions) { # Then, see where those variables were assigned $assigned = $var.GetAssignments() # (if they were assigned, keep moving) if ($assigned) { continue } # If there were not assigned $varName = $var.VariablePath.userPath.ToString() # add it to the list of parameters to create. $ParametersToCreate[$varName] = @( @( "[Parameter(ValueFromPipelineByPropertyName)]" "[$($AutoParameterType.FullName -replace '^System\.')]" "$var" ) -join [Environment]::NewLine ) } } } end { if ($NoEnd) { return } # Take all of the accumulated parameters and create a parameter block $newParamBlock = "param(" + [Environment]::newLine + $(@(foreach ($toCreate in $ParametersToCreate.GetEnumerator()) { $toCreate.Value -join [Environment]::NewLine }) -join (',' + [Environment]::NewLine)) + [Environment]::NewLine + ')' # If any parameters were passed in as ```[ScriptBlock]```s, if ($parameterScriptBlocks) { $parameterScriptBlocks += [ScriptBlock]::Create($newParamBlock) # join them with the new parameter block. $newParamBlock = $parameterScriptBlocks | Join-PipeScript -IncludeBlockType param } # If we did not provide a function name, if ((-not $FunctionName) -and $verb -and $noun # and we provided a verb and a noun ) { $FunctionName = "$Verb-$Noun" # set the function name. } # If we provided a -FunctionName, we'll be declaring a function. $functionDeclaration = # If the -FunctionType is function or filter if ($functionName -and $functionType -in 'function', 'filter') { # we declare it naturally. "$functionType $FunctionName {" } elseif ($FunctionName) { # Otherwise, we declare it as a command namespace "$functionType function $functionName {" # (which means we have to transpile). $NoTranspile = $false } # Create the script block by combining together the provided parts. $ScriptToBe = "$(if ($functionDeclaration) { "$functionDeclaration"}) $($allHeaders -join [Environment]::Newline) $newParamBlock $(if ($allDynamicParameters) { @(@("dynamicParam {") + $allDynamicParameters + '}') -join [Environment]::Newline }) $(if ($allBeginBlocks) { @(@("begin {") + $allBeginBlocks + '}') -join [Environment]::Newline }) $(if ($allProcessBlocks) { @(@("process {") + $allProcessBlocks + '}') -join [Environment]::Newline }) $(if ($allEndBlocks -and -not $allBeginBlocks -and -not $allProcessBlocks) { $allEndBlocks -join [Environment]::Newline } elseif ($allEndBlocks) { @(@("end {") + $allEndBlocks + '}') -join [Environment]::Newline }) $(if ($functionDeclaration) { '}'}) " $createdScriptBlock = [scriptblock]::Create($ScriptToBe) # If -NoTranspile was passed, $newPipeScriptOutput = if ($createdScriptBlock -and $NoTranspile) { $createdScriptBlock # output the script as-is } elseif ($createdScriptBlock) { # otherwise $createdScriptBlock | .>PipeScript # output the transpiled script. } if ($OutputPath) { "$newPipeScriptOutput" | Set-Content -Path $OutputPath if ($?) { Get-Item -Path $OutputPath } } else { $newPipeScriptOutput } } } |