Get-PwshAliasDefinition.ps1

#!/usr/bin/env pwsh
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest


function Get-PwshAliasDefinition {
    [CmdletBinding(DefaultParameterSetName = "ByName")]
    param(
        [Parameter(Mandatory=$false)]
        [string[]] $Path = @($pwd),

        [Parameter(Mandatory=$false, ParameterSetName = "ByName")]
        [ValidateNotNullOrEmpty()]
        [string] $Name,

        [Parameter(Mandatory=$true, ParameterSetName = "ByNameLike")]
        [ValidateNotNullOrEmpty()]
        [string] $NameLike,

        [Parameter(Mandatory=$true, ParameterSetName = "ByNameMatch")]
        [ValidateNotNullOrEmpty()]
        [string] $NameMatch,

        [Parameter(Mandatory=$false)]
        [ValidateRange(1, [int]::MaxValue)]
        [Nullable[int]] $First,

        [Parameter(Mandatory=$false)]
        [ValidateRange(1, [int]::MaxValue)]
        [Nullable[int]] $Last,

        [Parameter(Mandatory=$false)]
        [ValidateRange(0, [int]::MaxValue)]
        [Nullable[int]] $Skip,

        [Parameter(Mandatory=$false)]
        [ValidateRange(0, [int]::MaxValue)]
        [Nullable[int]] $SkipLast
    )
    # Outputs: Name, Value, FilePath, LineNumber, & Active.
    # ("Active" is a boolean indicating whether Get-Alias for that name returns the same value or not.)
    # Aliases may be defined redudantly in multiple files, possibly with conflicting values, so we need to check all of them.

    # At the end, we use Select-Object to filter our output.
    # So we go ahead and prepare the parameters for that.
    [hashtable] $selectObjectPagingParameters = @{}
    if ($First) {
        $selectObjectPagingParameters["First"] = $First.Value
    }
    if ($Last) {
        $selectObjectPagingParameters["Last"] = $Last.Value
    }
    if ($Skip) {
        $selectObjectPagingParameters["Skip"] = $Skip.Value
    }
    if ($SkipLast) {
        $selectObjectPagingParameters["SkipLast"] = $SkipLast.Value
    }

    [System.IO.FileInfo[]] $files = Get-ChildItem -Path $Path -Filter *.ps* -Recurse -File
    [hashtable] $resolvedPaths = @{}

    return (
        $files `
        | Select-String -AllMatches -Pattern "^\s*Set-Alias\s+(.+?)(#.*$|$|;.*$)"
        | ForEach-Object {
            # The match is the whole parameter set, not even split up yet.
            # Now, we need to split up and try to "parse" it.
            # We can't assume that all the parameters are present, so we need to be careful.

            [string[]] $aliasCommandParameters = ($_.Matches[0].Groups[1].Value.Trim()) -split "\s+"
            [string] $aliasName = $null
            [string] $aliasValue = $null
            for ($i = 0; $i -lt $aliasCommandParameters.Length; $i++) {
                [string] $parameter = $aliasCommandParameters[$i]
                if ($parameter -eq "-Name") {
                    $aliasName = $aliasCommandParameters[$i + 1]
                    $i++
                } elseif ($parameter -eq "-Value") {
                    $aliasValue = $aliasCommandParameters[$i + 1]
                    $i++
                } elseif ($parameter -in @("-Option", "-Description")) {
                    # We don't care about these values.
                    $i++
                } elseif ($parameter -in @("-Force", "-PassThru")) {
                    # We don't care about these switches either.
                } elseif (-not $parameter.StartsWith("-")) {
                    if (-not $aliasName) {
                        $aliasName = $parameter
                    } elseif (-not $aliasValue) {
                        $aliasValue = $parameter
                    } else {
                        throw "Unexpected parameter '$parameter' in alias definition at line $($_.LineNumber) of $($_.Path)."
                    }
                } else {
                    throw "Unexpected parameter '$parameter' in alias definition at line $($_.LineNumber) of $($_.Path)."
                }
                if ($aliasName -and $aliasValue)
                {
                    break
                }
            }

            [PSCustomObject]@{
                "Name"       = $aliasName;
                "Value"      = $aliasValue;
                "FilePath"   = $_.Path;
                "LineNumber" = $_.LineNumber;
                "DeclarationContext" = $_.ToEmphasizedString($PWD);
            }
        } `
        | ForEach-Object {
            # Remove quotes from the Name and the Value.
            $_.Name = $_.Name.Trim("`"")
            $_.Value = $_.Value.Trim("`"")

            # Make the paths relative, so that the output is more readable.
            if (-not $resolvedPaths.ContainsKey($_.FilePath)) {
                $resolvedPaths[$_.FilePath] = Resolve-Path -Path $_.FilePath -Relative
            }
            $_.FilePath = $resolvedPaths[$_.FilePath]

            $nameMatchedExtantAlias = Get-Alias -Name $_.Name -ErrorAction SilentlyContinue

            [bool] $isActive = ($nameMatchedExtantAlias -and $nameMatchedExtantAlias.Definition -eq $_.Value)
            [bool] $nameIsDynamic = ($_.Name -like "*`$*")
            [bool] $valueIsDynamic = ($_.Value -like "*`$*")

            # Add the properties to the pipeline object.
            $_ `
            | Add-Member -MemberType NoteProperty -Name IsActive -Value $isActive -PassThru `
            | Add-Member -MemberType NoteProperty -Name NameIsDynamic -Value $nameIsDynamic -PassThru `
            | Add-Member -MemberType NoteProperty -Name ValueIsDynamic -Value $valueIsDynamic -PassThru
        } `
        | Where-Object {
            if ($Name) {
                $_.Name -eq $Name
            } elseif ($NameLike) {
                $_.Name -like $NameLike
            } elseif ($NameMatch) {
                $_.Name -match $NameMatch
            } else {
                $true
            }
        } `
        | Select-Object @selectObjectPagingParameters
    )
}