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}
                        }
                    }
                }
            }
        }
    }
}