Private/Get-Parameter.ps1

# Borrowed from http://poshcode.org/5929 with a minor tweak for validateset - thanks all!
Function Get-Parameter {
    #.Synopsis
    # Enumerates the parameters of one or more commands
    #.Description
    # Lists all the parameters of a command, by ParameterSet, including their aliases, type, etc.
    #
    # By default, formats the output to tables grouped by command and parameter set
    #.Example
    # Get-Command Select-Xml | Get-Parameter
    #.Example
    # Get-Parameter Select-Xml
    [CmdletBinding(DefaultParameterSetName = "ParameterName")]
    param(
        # The name of the command to get parameters for
        [Parameter(Position = 1, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Name")]
        [string[]]$CommandName,

        # The parameter name to filter by (allows Wilcards)
        [Parameter(Position = 2, ValueFromPipelineByPropertyName = $true, ParameterSetName = "FilterNames")]
        [string[]]$ParameterName = "*",

        # The ParameterSet name to filter by (allows wildcards)
        [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = "FilterSets")]
        [string[]]$SetName = "*",

        # The name of the module which contains the command (this is for scoping)
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        $ModuleName,

        # Skip testing for Provider parameters (will be much faster)
        [Switch]$SkipProviderParameters,

        # Forces including the CommonParameters in the output
        [switch]$Force
    )

    begin {
        $PropertySet = @( "Name",
            @{n = "Position"; e = { if ($_.Position -lt 0) { "Named" }else { $_.Position } } },
            "Aliases", 
            @{n = "Short"; e = { $_.Name } },
            @{n = "Type"; e = { $_.ParameterType.Name } }, 
            @{n = "ParameterSet"; e = { $paramset } },
            @{n = "Command"; e = { $command } },
            @{n = "Mandatory"; e = { $_.IsMandatory } },
            @{n = "Provider"; e = { $_.DynamicProvider } },
            @{n = "ValueFromPipeline"; e = { $_.ValueFromPipeline } },
            @{n = "ValueFromPipelineByPropertyName"; e = { $_.ValueFromPipelineByPropertyName } },
            "ValidateSetValues" # This is a bit specific and not always applicable, but need it for this project....
        )
        function Join-Object {
            Param(
                [Parameter(Position = 0)]
                $First,

                [Parameter(ValueFromPipeline = $true, Position = 1)]
                $Second
            )
            begin {
                [string[]] $p1 = $First | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name
            }
            process {
                $Output = $First | Select-Object $p1
                foreach ($p in $Second | Get-Member -MemberType Properties | Where-Object { $p1 -notcontains $_.Name } | Select-Object -ExpandProperty Name) {
                    Add-Member -InputObject $Output -MemberType NoteProperty -Name $p -Value $Second."$p"
                }
                $Output
            }
        }

        function Add-Parameters {
            [CmdletBinding()]
            param(
                [Parameter(Position = 0)]
                [Hashtable]$Parameters,

                [Parameter(Position = 1)]
                [System.Management.Automation.ParameterMetadata[]]$MoreParameters
            )

            foreach ($p in $MoreParameters | Where-Object { !$Parameters.ContainsKey($_.Name) } ) {
                Write-Debug ("INITIALLY: " + $p.Name)
                $Parameters.($p.Name) = $p | Select-Object *
            }

            [Array]$Dynamic = $MoreParameters | Where-Object { $_.IsDynamic }
            if ($dynamic) {
                foreach ($d in $dynamic) {
                    if (Get-Member -InputObject $Parameters.($d.Name) -Name DynamicProvider) {
                        Write-Debug ("ADD:" + $d.Name + " " + $provider.Name)
                        $Parameters.($d.Name).DynamicProvider += $provider.Name
                    }
                    else {
                        Write-Debug ("CREATE:" + $d.Name + " " + $provider.Name)
                        $Parameters.($d.Name) = $Parameters.($d.Name) | Select-Object *, @{ n = "DynamicProvider"; e = { @($provider.Name) } }
                    }
                } 
            }
        }
    }

    process {
        foreach ($cmd in $CommandName) {
            if ($ModuleName) { $cmd = "$ModuleName\$cmd" }
            Write-Verbose "Searching for $cmd"
            $commands = @(Get-Command $cmd)

            foreach ($command in $commands) {
                Write-Verbose "Searching for $command"
                # resolve aliases (an alias can point to another alias)
                while ($command.CommandType -eq "Alias") {
                    $command = @(Get-Command ($command.definition))[0]
                }
                if (-not $command) { continue }

                Write-Verbose "Get-Parameters for $($Command.Source)\$($Command.Name)"

                $Parameters = @{}

                ## We need to detect provider parameters ...
                $NoProviderParameters = !$SkipProviderParameters
                ## Shortcut: assume only the core commands get Provider dynamic parameters
                if (!$SkipProviderParameters -and $Command.Source -eq "Microsoft.PowerShell.Management") {
                    ## The best I can do is to validate that the command has a parameter which could accept a string path
                    foreach ($param in $Command.Parameters.Values) {
                        if (([String[]], [String] -contains $param.ParameterType) -and ($param.ParameterSets.Values | Where-Object { $_.Position -ge 0 })) {
                            $NoProviderParameters = $false
                            break
                        }
                    }
                }

                if ($NoProviderParameters) {
                    if ($Command.Parameters) {
                        Add-Parameters $Parameters $Command.Parameters.Values
                    }
                }
                else {
                    foreach ($provider in Get-PSProvider) {
                        if ($provider.Drives.Length -gt 0) {
                            $drive = Get-Location -PSProvider $Provider.Name
                        }
                        else {
                            $drive = "{0}\{1}::\" -f $provider.ModuleName, $provider.Name
                        }
                        Write-Verbose ("Get-Command $command -Args $drive | Select -Expand Parameters")

                        try {
                            $MoreParameters = (Get-Command $command -Args $drive).Parameters.Values
                        }
                        catch {}
       
                        if ($MoreParameters.Length -gt 0) {
                            Add-Parameters $Parameters $MoreParameters
                        }
                    }
                    # If for some reason none of the drive paths worked, just use the default parameters
                    if ($Parameters.Length -eq 0) {
                        if ($Command.Parameters) {
                            Add-Parameters $Parameters $Command.Parameters.Values
                        }
                    }
                }

                ## Calculate the shortest distinct parameter name -- do this BEFORE removing the common parameters or else.
                $Aliases = $Parameters.Values | Select-Object -ExpandProperty Aliases  ## Get defined aliases
                $ParameterNames = $Parameters.Keys + $Aliases
                foreach ($p in $($Parameters.Keys)) {
                    $short = "^"
                    $aliases = @($p) + @($Parameters.$p.Aliases) | Sort-Object { $_.Length }
                    $shortest = "^" + @($aliases)[0]

                    foreach ($name in $aliases) {
                        $short = "^"
                        foreach ($char in [char[]]$name) {         
                            $short += $char
                            $mCount = ($ParameterNames -match $short).Count
                            if ($mCount -eq 1 ) {
                                if ($short.Length -lt $shortest.Length) {
                                    $shortest = $short
                                }
                                break
                            }
                        }
                    }
                    if ($shortest.Length -lt @($aliases)[0].Length + 1) {
                        # Overwrite the Aliases with this new value
                        $Parameters.$p = $Parameters.$p | Add-Member NoteProperty Aliases ($Parameters.$p.Aliases + @("$($shortest.SubString(1))*")) -Force -PassThru
                    }

                    # ValidateSet...
                    $Parameters.$p = $Parameters.$p | Add-Member NoteProperty ValidateSetValues ($Parameters.$p.Attributes | Where-Object { $_.TypeId.name -like 'ValidateSetAttribute' }).ValidValues -Force -PassThru

                }

                # Write-Verbose "Parameters: $($Parameters.Count)`n $($Parameters | ft | out-string)"
                $CommonParameters = [string[]][System.Management.Automation.Cmdlet]::CommonParameters

                foreach ($paramset in @($command.ParameterSets | Select-Object -ExpandProperty "Name")) {
                    $paramset = $paramset | Add-Member -Name IsDefault -MemberType NoteProperty -Value ($paramset -eq $command.DefaultParameterSet) -PassThru
                    foreach ($parameter in $Parameters.Keys | Sort-Object) {
                        # Write-Verbose "Parameter: $Parameter"
                        if (!$Force -and ($CommonParameters -contains $Parameter)) { continue }
                        if ($Parameters.$Parameter.ParameterSets.ContainsKey($paramset) -or $Parameters.$Parameter.ParameterSets.ContainsKey("__AllParameterSets")) {
                            if ($Parameters.$Parameter.ParameterSets.ContainsKey($paramset)) {
                                $output = Join-Object $Parameters.$Parameter $Parameters.$Parameter.ParameterSets.$paramSet 
                            }
                            else {
                                $output = Join-Object $Parameters.$Parameter $Parameters.$Parameter.ParameterSets.__AllParameterSets
                            }

                            Write-Output $Output | Select-Object $PropertySet | ForEach-Object {
                                $null = $_.PSTypeNames.Insert(0, "System.Management.Automation.ParameterMetadata")
                                $null = $_.PSTypeNames.Insert(0, "System.Management.Automation.ParameterMetadataEx")
                                # Write-Verbose "$(($_.PSTypeNames.GetEnumerator()) -join ", ")"
                                $_
                            } |
                                Add-Member ScriptMethod ToString { $this.Name } -Force -PassThru |
                                Where-Object { $(foreach ($pn in $ParameterName) { $_ -like $Pn }) -contains $true } |
                                Where-Object { $(foreach ($sn in $SetName) { $_.ParameterSet -like $sn }) -contains $true }
                        }
                    }
                }
            }
        }
    }
}