Private/Get-Parameters.ps1

Function Get-Parameters
    {
        <#
        .SYNOPSIS
        Extracts parameter information from a PowerShell function script file, including both static and dynamic parameters.
        .EXAMPLE
        Get-Parameters -CmdletPath C:\Path\To\Your\Cmdlet.ps1
        #>


        [CmdletBinding()]
        param(
            [Parameter(Mandatory=$true, Position=0, HelpMessage="The file path to the PowerShell function script file to extract parameters from.")]
            [string]$CmdletPath
        )

        $Body = Get-Content $CmdletPath -Raw -Encoding UTF8
        $Body = $Body -replace '^\s*function\s+\S+\s*{', ''
        $Body = $Body -replace '}\s*$', ''
        $Body = $Body.Trim()


        $AbstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($Body, [ref]$null, [ref]$null)

        If($Errors.Count -gt 0)
            {
                Throw "Parse errors in $CmdletPath"
            }

        # --- Static parameters ---
        $ParamBlock = $AbstractSyntaxTree.ParamBlock
        $DynamicParamAst = $AbstractSyntaxTree.DynamicParamBlock

        [System.Collections.ArrayList]$ParameterArrayList = @()

        [System.Collections.Generic.List[string]]$ExcludedParameters = [System.Management.Automation.PSCmdlet]::CommonParameters + [System.Management.Automation.PSCmdlet]::OptionalCommonParameters

        If($ParamBlock)
            {
                ForEach($Parameter in $ParamBlock.Parameters)
                    {
                        # Parameter name
                        $Name = $Parameter.Name.VariablePath.UserPath
                        $Type = If($Parameter.StaticType) { $Parameter.StaticType.FullName } Else { 'Non Specified' }
                        $DefaultValue = If($Parameter.DefaultValue) { $Parameter.DefaultValue } Else { $null }

                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parameter Name: $Name"
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parameter Type: $Type"
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parameter DefaultValue: $DefaultValue"

                        [System.Collections.ArrayList]$ParameterOptions = @()
                        ForEach($Attribute in $Parameter.Attributes)
                            {
                                $AttributeName = $Attribute.TypeName.Name
                                If(@('Parameter','ValidateSet', 'ValidateRange', 'ValidateScript') -notcontains $AttributeName)
                                    {
                                        Continue
                                    }

                                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parameter attribute name: $AttributeName"
                                $Options = @{}

                                # Named arguments (eg Mandatory=$true)
                                ForEach($Named in $Attribute.NamedArguments)
                                    {
                                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parameter named argument: $Named"
                                        $Options[$Named.ArgumentName] = $Named.Argument.Extent.Text.Trim("'")
                                    }

                                # Positional args (eg ValidateSet('a','b'))
                                If($Attribute.PositionalArguments.Count -gt 0)
                                    {
                                        $Options[$AttributeName] = $Attribute.PositionalArguments | ForEach-Object { $_.Extent.Text.Trim("'") }
                                    }

                                [void]$ParameterOptions.Add($Options)
                            }

                        [void]$ParameterArrayList.Add([pscustomobject]@{
                            Name         = $Name
                            Type         = $Type
                            DefaultValue = $DefaultValue
                            Options      = $ParameterOptions
                        })
                    }
            }

        # --- Dynamic parameters ---
        If($DynamicParamAst)
            {
                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Found DynamicParam block, extracting dynamic parameters..."

                # Keep track of already added names
                $ExistingNames = $ParameterArrayList | Select-Object -ExpandProperty Name

                $Command = Get-Command -Name ((Get-Item $CmdletPath).BaseName) -ErrorAction SilentlyContinue
                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Command parameters: $($Command.Parameters.Keys -join ', ')"
                ForEach($RuntimeParam in $Command.Parameters.Values)
                    {
                        If(($ExcludedParameters -contains $RuntimeParam.Name) -or ($ExistingNames -contains $RuntimeParam.Name))
                            {
                                Continue
                            }

                        $Name = $RuntimeParam.Name
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Dynamic Parameter Name: $Name"
                        $Type = If($RuntimeParam.ParameterType) { $RuntimeParam.ParameterType.Name } Else { 'Non Specified' }
                        $DefaultValue = If($RuntimeParam.DefaultValue) { $RuntimeParam.DefaultValue } Else { $null }

                        # Collect Options from Attributes
                        $Options = @{}
                        ForEach($Attribute in $RuntimeParam.Attributes)
                            {
                                # Named arguments
                                ForEach($Property in $Attribute.PSObject.Properties)
                                    {
                                        $Options[$Property.Name] = $Property.Value
                                    }
                            }

                        [void]$ParameterArrayList.Add([pscustomobject]@{
                            Name         = $Name
                            Type         = $Type
                            DefaultValue = $DefaultValue
                            Options      = $Options
                        })
                    }
            }

        If($ParameterArrayList.Count -ne 1)
            {
                $ParameterArrayList = $ParameterArrayList | Sort-Object {$_.Options.Position}
            }

        Return [pscustomobject]@{
            Name       = $CmdletName
            Parameters = $ParameterArrayList
        }
    }