dynpar.psm1

$code = @'
using System;
using System.Collections.Generic;
using System.Management.Automation;
 
    public class DynamicAttribute : System.Attribute
    {
        private ScriptBlock sb;
 
        public DynamicAttribute(ScriptBlock condition)
        {
            sb = condition;
        }
    }
'@


$null = Add-Type -TypeDefinition $code *>&1

function Get-PsoDynamicParameterDefinition
{
    <#
            .SYNOPSIS
            takes a scriptblock with a static param() parameter definition and returns a PowerShell function where designated parameters are turned into dynamic parameters.
 
            .DESCRIPTION
            Module adds a new attribute named [Dynamic()] that can be used to turn static parameters into dynamic parameters.
            For this to work, submit a scriptblock to the attribute. When the scriptblock evaluates to $true, the parameter is visible, else it is not available.
 
            .EXAMPLE
            Get-PsoDynamicParameterDefinition -ScriptBlock Value -FunctionName Value
            Takes the parameter definition from $parameters, turns all parameters designated with the attribute [Dynamic()] into dynamic parameters and emits the new function body
 
            .EXAMPLE
            Get-PsoDynamicParameterDefinition -FunctionInfo Value
            Describe what this call does
 
            .LINK
            https://github.com/TobiasPSP/Modules.dynamicparam
    #>


    [CmdletBinding()]
    param
    (
        # A scriptblock with a param() block. Assign the attribute [Dynamic()] to all parameters that you want to convert to a dynamic parameter.
        [Parameter(Mandatory,ValueFromPipeline)]
        [ScriptBlock]
        $ScriptBlock,

        # Name of the function to be created. Can be anything, should adhere to common Verb-Noun syntax.
        [string]
        $FunctionName = 'Get-Something'
    )

    begin
    {
        # common parameters
        $commonParameters = 'Verbose','Debug','ErrorAction','WarningAction','InformationAction','ErrorVariable','WarningVariable','InformationVariable','OutVariable','OutBuffer','PipelineVariable'
    }

    process
    {
        $beginBlock = "# place your own code that executes before pipeline processing starts"
        $processBlock = '# place your own code that executes for each incoming pipeline object'
        $endBlock = '# place your own code that executes after pipeline processing has finished'

        # collect generated code:
        [System.Text.StringBuilder]$result = ''

        # store parameter default values:
        $defaultValues = @{}

        # store list of dynamic parameters:
        $dynParamList = [System.Collections.ObjectModel.Collection[string]]::new()

        # store list of static parameters:
        $paramList = [System.Collections.ObjectModel.Collection[string]]::new()

        # store list of pipeline-aware parameters:
        $pipelineAttribs = 'ValueFromPipeline', 'ValueFromPipelineByPropertyName'
        $pipelineParamList = [System.Collections.ObjectModel.Collection[string]]::new()

        $null = $result.AppendLine("function $FunctionName")
        $null = $result.AppendLine('{')

        # extract the content of the param() block from the submitted scriptblock:
        $pb = $ScriptBlock.Ast.FindAll({$args[0] -is [System.Management.Automation.Language.ParamBlockAst]}, $false)

        $param = $pb[0]

        # add attributes
        foreach($_ in $param.Attributes)
        {
            $null = $result.AppendLine(' ' + $_.Extent.Text)
        }

        $null = $result.AppendLine(@'
    # MUST be an advanced function so make sure you add [CmdletBinding()] just to be sure:
    [CmdletBinding()]
    param
    (
        ##StaticParams##
    )
 
    dynamicparam
    {
        # create container for all dynamically created parameters:
        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
 
'@
)

    
        $param.Parameters | ForEach-Object {
            $parameter = $_

            # check to see if this parameter was decorated with a [Dynamic()] attribute:
            $dynamicAttribute = $parameter.Attributes.Where{ $_.TypeName.FullName -eq 'Dynamic' } | Select-Object -First 1

            # if so, add to dynamic parameters:
            if ($dynamicAttribute)
            {
                $condition = $dynamicAttribute.PositionalArguments[0].Extent.Text -replace '^{' -replace '}$'
            
                $name = $parameter.Name.VariablePath.UserPath
                $dynParamList.Add($name)
                $null = $result.AppendLine('')
                $null = $result.AppendLine(" #region Start Parameter -${name} ####")
                if ($condition)
                {
                    $null = $result.AppendLine(" if ($condition) {")
                }
                $null = $result.AppendLine(" # create container storing all attributes for parameter -$name")
                $null = $result.AppendLine(' $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()')
                $null = $result.AppendLine('')
            
                $conflicts = $commonParameters -like "$name*"
                if ($conflicts.Count -gt 0)
                {
                    Write-Warning ('Parameter -{0} conflicts with built-in parameters {1}. Rename -{0}.' -f $name, ('-' + ($conflicts -join ', -')))
                }

                $defaultValue = $parameter.DefaultValue.Extent.Text
            
                $theType = 'Object'

                $hasParameterAttribute = $false
        
                $parameter.Attributes | ForEach-Object {
                    $attribute = $_
                    switch ($attribute.GetType().FullName)
                    {
                        'System.Management.Automation.Language.TypeConstraintAst'  { 
                            $theType = $attribute.TypeName.FullName
                        }
                        'System.Management.Automation.Language.AttributeAst' {
                            $typeName = $attribute.TypeName.FullName
                            if ($typename -ne 'Dynamic')
                            {
                                if (!$hasParameterAttribute -and $typename -eq 'Parameter') { $hasParameterAttribute = $true }
                                [string]$positionals = $attribute.PositionalArguments.Extent.Text -join ','
                                $null = $result.AppendLine((' # Define attribute [{0}()]:' -f $attribute.TypeName.FullName))
                                $null = $result.AppendLine((' $attrib = [{0}]::new({1})' -f $attribute.TypeName.FullName, $positionals))
                                $attribute.NamedArguments | ForEach-Object {
                                    $namedAttributeExpression = $_.ToString()
                                    if ($_.ExpressionOmitted)
                                    { $namedAttributeExpression += '=$true'}
                            
                                    $null = $result.AppendLine((' $attrib.{0}' -f $namedAttributeExpression))

                                    # if parameter is pipeline-aware, remember it:
                                    if ($_.ArgumentName -in $pipelineAttribs -and $pipelineParamList.Contains($Name) -eq $false) 
                                    { $pipelineParamList.Add($name) }
                                }
                                $null = $result.AppendLine(' $attributeCollection.Add($attrib)')
                                $null = $result.AppendLine('')
                            }
                        }
                        default {
                            Write-Warning "Unexpected Type: $_"
                        }
                    }            
            
                }
                if (!$hasParameterAttribute)
                {
                    $null = $result.AppendLine(' # Define attribute [Parameter()]:')
                    $null = $result.AppendLine(' $attrib = [Parameter]::new()')
                    $null = $result.AppendLine(' $attributeCollection.Add($attrib)')
                    $null = $result.AppendLine('')
                }
                $null = $result.AppendLine(' # compose dynamic parameter:')
                $null = $result.AppendLine((' $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new({0},{1},$attributeCollection)' -f "'$Name'", "[$theType]"))
                if ($theType -eq 'Object')
                { Write-Warning ('Parameter -{0} currently is of type [Object]. Using an appropriate type constraint like [string], [int], [datetime] etc in your parameter definition is recommended.' -f $Name) }

                # store parameter default value:
                if ($defaultValue -ne $null)
                {
                    $defaultValues[$name] = $defaultValue
                }
                $null = $result.AppendLine('')
                $null = $result.AppendLine(' # add parameter to parameter collection:')
                $null = $result.AppendLine((' $paramDictionary.Add({0},$dynParam)' -f "'$Name'"))
                if ($condition)
                {
                    $null = $result.AppendLine(" }")
                }
                $null = $result.AppendLine(" #endregion End Parameter -${name} ####")
                $null = $result.AppendLine('')
            }
            # else, add to static parameters
            else
            {
                $paramList.Add($parameter.Extent.Text)
            }
        }

    
        # determine the maximum parameter name length for formatting purposes:
        $longest = 0
        foreach($_ in $dynParamList)
        {
            $longest = [Math]::Max($longest, $_.Length)
        }

        $null = $result.AppendLine(@'
 
        # return dynamic parameter collection:
        $paramDictionary
    }
     
    begin
    {
         
'@
)
    
        $null = $result.AppendLine(' #region initialize variables for dynamic parameters')
        foreach($varName in $dynParamList)
        {
            $null = $result.AppendLine('')
            $null = $result.AppendLine((' if($PSBoundParameters.ContainsKey(''{0}'')) {{ ${0} = $PSBoundParameters[''{0}''] }}' -f $varName))
            if ($defaultValues.ContainsKey($varName))
            {
                $null = $result.AppendLine((' else {{ ${0} = {1} }}' -f $varName, $defaultValues[$varName]))        
            }
            else
            {
                $null = $result.AppendLine((' else {{ ${0} = $null}}' -f $varName))
            }
        }
        $null = $result.AppendLine(' #endregion initialize variables for dynamic parameters')
        $null = $result.AppendLine(@"
 
        $beginBlock
    }
  
    process
    {
"@
)
        if ($pipelineAttribs.Count -gt 0)
        {
            $null = $result.AppendLine(' #region update variables for pipeline-aware parameters:')
            foreach($varName in $pipelineParamList)
            {
                $null = $result.AppendLine((' if ($PSBoundParameters.ContainsKey(''{0}'')) {{ ${0} = $PSBoundParameters[''{0}''] }}' -f $varName))
            }
            $null = $result.AppendLine(' #endregion update variables for pipeline-aware parameters')
            $null = $result.AppendLine(@'
         
        #region output pipeline-aware parameters for diagnostic purposes:
        [PSCustomObject]@{
            ParameterSetName = $PSCmdlet.ParameterSetName
'@
)
            foreach($varName in $pipelineParamList)
            {
                # pad the parameter name so the assignments are aligned:
                $null = $result.AppendLine((' {0} = ${1}' -f $varName.PadRight($longest), $varName))
            }
            $null = $result.AppendLine(@'
        } | Format-List
 
        #endregion output pipeline-aware parameters for diagnostic purposes
'@
)
        }
        $null = $result.AppendLine(@"
 
        $processBlock
    }
 
    end
    {
        #region output submitted parameters for diagnostic purposes:
        [PSCustomObject]@{
"@
)
    
        $dynParamList | Sort-Object | ForEach-Object {
            # pad the parameter name so the assignments are aligned:
            $null = $result.AppendLine((' {0} = ${1}' -f $_.PadRight($longest), $_))
        }
        $null = $result.AppendLine(@"
        } | Format-List
 
        #endregion output submitted parameters for diagnostic purposes
 
        $endBlock
    }
}
"@
)

        # insert list of static parameters (if any present)
        # into param() inside the composed code:
        # turn array of parameters in comma-separated list:
        $staticParams = $paramList -join ",`r`n`r`n "
        # replace placeholder in the result with the static parameter list:
        $null = $result.Replace('##StaticParams##', $staticParams)

        # return composed script code:
        return [ScriptBlock]::Create($result)
    }
}

Set-Alias -Name Get-DynamicParameterDefinition -Value Get-PsoDynamicParameterDefinition
Set-Alias -Name Get-DynamicParameterDefinition -Value gdp