functions/public/Get-FunctionProfile.ps1

Function Get-FunctionProfile {
    [cmdletbinding()]
    [alias("gfp")]
    [OutputType("PSFunctionProfile")]
    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipelineByPropertyName,
            HelpMessage = "Specify the name of the PowerShell function."
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            HelpMessage = "Specify the path to the .ps1 or .psm1 file."
        )]
        [ValidateScript({
                If (Test-Path $_ ) {
                    $True
                    If ($_ -match "\.ps(m)?1$") {
                        $True
                    }
                    Else {
                        Throw "The path must be to a .ps1 or .psm1 file."
                        $False
                    }
                }
                Else {
                    Throw "Can't validate that $_ exists. Please verify and try again."
                    $False
                }
            })]
        [string]$Path
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"

        New-Variable astTokens -Force
        New-Variable astErr -Force
    } #begin

    Process {
        $Name = Format-FunctionName $Name
        $Path = Convert-Path -Path $path
        $cmds = [System.Collections.Generic.list[string]]::new()
        $aliasList = [System.Collections.Generic.list[string]]::new()
        $Unresolved = [System.Collections.Generic.list[string]]::new()
        $dotnet = [System.Collections.Generic.list[string]]::new()
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Profiling function $name in $path"

        $fa = Get-FunctionAttribute @PSBoundParameters
        $pb = Get-ParameterBlock @PSBoundParameters
        $attrib = ($pb.parameters.attributes).where({ $_.NamedArguments.Argumentname -eq "ParameterSetName" }).namedarguments | Where-Object { $_.ArgumentName -eq 'parameterSetName' }
        $req = Get-PSRequirements -Path $Path

        $AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)

        $fd = $AST.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -AND $args[0].name -eq $Name }, $True)
        $content = $fd.Extent.Text
        $funAST = [System.Management.Automation.Language.Parser]::ParseInput($content, [ref]$astTokens, [ref]$astErr)

        #find .NET expressions
        $t = $funAST.findAll({ $args[0] -is [System.Management.Automation.Language.TypeExpressionAst] }, $true)
        if ($t) {
            foreach ($item in $t) {
                $value = $item.parent.Extent.text.ToLower()
                if (-Not $dotnet.Contains($value)) {
                    $dotnet.add($value)
                }
            }
        }
        #get commands
        $rawlist = ($asttokens).where({ ($_.tokenflags -contains 'commandname') -AND ($_.kind -eq 'generic') }).Text.ToLower() |
        Select-Object -Unique
        foreach ($item in $rawlist) {
            if ($item -match "^\w+-\w+$") {
                $item = Format-FunctionName -Name $item
            }
            if (-Not $cmds.Contains($item)) {
                Write-Verbose "found command $item"
                $cmds.Add($item)
            }
        }

        #get aliases
        $aliases = ($asttokens).where({ ($_.tokenflags -contains 'commandname') -AND ($_.kind -eq 'identifier') })
        if ($aliases) {
            Write-Verbose "Detected $($aliases.count) aliases"
            $filteredAliases = $aliases.Text.toLower() | Select-Object -Unique
            foreach ($alias in $filteredAliases) {
                Try {
                    $a = Get-Alias -Name $alias -ErrorAction stop
                    if (-Not $cmds.Contains($a.Definition)) {
                        Write-Verbose "Adding resolved command $($a.definition)"
                        $cmds.Add($a.Definition)
                    }
                    if (-Not $aliasList.Contains($alias)) {
                        Write-Verbose "Saving alias ($alias)"
                        $aliasList.Add($alias)
                    }
                }
                Catch {
                    Write-Verbose "Could not resolve potential alias $alias"
                    $Unresolved.Add($alias)
                }
            }
        }
        #separate PowerShell cmdlets from external commands
        $cmdlets, $external = $cmds.Where({ $_ -notmatch ".*(exe|bat|sh|cmd|vbs|wsf)$" }, "Split")
        [pscustomobject]@{
            PSTypeName            = "PSFunctionProfile"
            Name                  = $Name
            FunctionAlias         = $fa.where({ $_.type -eq 'alias' }).PositionalArguments.Value
            SupportsShouldProcess = $fa.where({ $_.type -eq 'cmdletbinding' }).NamedArguments.argumentname -contains "SupportsShouldProcess"
            ParameterSets         = $attrib.argument.value | Select-Object -Unique
            DynamicParameters     = (Get-Content $path | Select-String '^(\s+)?DynamicParam\s+{') ?  $True : $False
            RequiredVersion       = $req.RequiredPSVersion
            RequiredModules       = $req.RequiredModules
            RequiresElevation     = $req.IsElevationrequired
            Commands              = $cmdlets | Sort-Object
            ExternalCommands      = $external | Sort-Object
            DotNet                = $dotnet
            Aliases               = $aliasList
            Unresolved            = $Unresolved
            Path                  = $Path
        }
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"

    } #end

} #close Get-FunctionProfile