functions/Find-Path.ps1

function Find-WUPath {
    <#
        .SYNOPSIS
        Search for paths in all locations.
 
        .DESCRIPTION
        Search the path everywhere using command path or es.exe.
 
        .OUTPUTS
        System.String.
 
        Returns the path found by searching.
 
        .EXAMPLE
        PS C:\> Find-WUPath 'powershell.exe'
 
        In this example, Finds and returns the path where the leaf contains powershell.exe.
 
        .EXAMPLE
        PS C:\> Find-WUPath 'powershell.exe' -Strict
 
        In this example, Finds and returns the path where the leaf exactly matches powershell.exe.
 
        .EXAMPLE
        PS C:\> Find-WUPath 'PowerShell\v1.0\powershell.exe' -Strict
 
        In this example, Searches for and returns a path whose leaf exactly matches powershell.exe and whose parent path contains PowerShell\v1.0.
 
        .EXAMPLE
        PS C:\> Find-WUPath 'powershell.exe' -Strict -Exclude 'Windows'
 
        This example searches for a path whose path does not match'Windows' and whose filename is powershell.exe.
 
        .EXAMPLE
        PS C:\> Find-WUPath 'powershell.exe' -Program
 
        In this example, powershell.exe is searched for in the order of command, start menu shortcut file link destination, es.exe, and the path containing powershell.exe in the leaf is returned.
    #>


    [CmdletBinding()]
    param (
        # Specify the character string contained in the leaf of the path to be searched. In addition, the strings contained in its parent directory can be specified before the leaf, separated by / or \. However, if -Strict is specified, the path with exact leaf matches will be searched. Also, wildcards are not supported.
        [Parameter(Mandatory,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Name,

        # Specifies the regular expression for the path to exclude. It is case sensitive. Paths that match the following strings are excluded, regardless of the value specified for this parameter.
        # 'C:\\Windows\\SysWOW64'
        # 'SxS\\'
        # 'AppData\\Local\\Microsoft\\Windows\\FileHistory'
        # 'C:\\Windows\\Prefetch'
        # 'AppData\\Roaming\\Microsoft\\Windows\\Recent'
        # 'scoop\\apps\\.+\\_.+\.old\\'
        [string[]]
        $Exclude,

        # Search for a path that has an exact leaf match.
        [switch]
        $Strict,

        # Searches the command from the leaf of the path specified by -Name and returns the path if found. If not found, searches all locations by using es.exe.
        [switch]
        $Program
    )

    begin {
        Set-StrictMode -Version 'Latest'

        $isCompleated = {
            param (
                [string[]]
                $AddPath
            )

            if (!$AddPath) {
                return $false
            }

            $resultPaths.AddRange($AddPath)
            $resultItems = Get-Item -LiteralPath $resultPaths -ErrorAction Ignore

            if ($Exclude) {
                foreach ($aExclude in $Exclude) {
                    $resultItems = $resultItems |
                    Where-Object { !($_.FullName -cmatch $aExclude) }
                }
            }

            $resultItems = $patterns | ForEach-Object {
                $pattern = $_

                $completedItem = $resultItems |
                Where-Object {
                    if (!$pattern.Parent) {
                        return $true
                    }
                    return Select-String -InputObject (Split-Path $_ -Parent) -SimpleMatch $pattern.Parent
                } |
                Where-Object {
                    if ($Strict) {
                        return $_.Name -eq $pattern.Leaf
                    }
                    return Select-String -InputObject $_.Name -SimpleMatch $pattern.Leaf
                }

                if ($completedItem) {
                    $completedItem
                    $completedLeaves.add($pattern.Leaf) | Out-Null
                }
            }

            $resultPaths.Clear()
            if (!$resultItems) {
                return $false
            }
            $resultPaths.AddRange(@(Convert-Path -LiteralPath $resultItems.FullName | Select-Object -Unique))

            # Narrow down $leaves to incomplete ones
            $leaves = $leaves |
            Where-Object { $completedLeaves -notcontains $_ }

            $completedLeaves.Clear()

            return !$leaves
        }

        $names = @()
    }

    process {
        foreach ($aName in $Name) {
            # Unified path separator to backslash (\)
            $names += $aName -replace '/', '\'
        }
    }

    end {
        $patterns = $names | ForEach-Object {
            @{
                Leaf   = Split-Path $_ -Leaf
                Parent = Split-Path $_ -Parent
            }
        }

        $leaves = $patterns.Leaf
        $resultPaths = New-Object System.Collections.ArrayList
        $completedLeaves = New-Object System.Collections.ArrayList

        $Exclude += @(
            [regex]::Escape("C:\Windows\SysWOW64")
            [regex]::Escape("SxS\")
            [regex]::Escape("AppData\Local\Microsoft\Windows\FileHistory")
            [regex]::Escape("C:\Windows\Prefetch")
            [regex]::Escape("AppData\Roaming\Microsoft\Windows\Recent")
            "scoop\\apps\\.+\\_.+\.old\\"
        )
        if ($env:ChocolateyInstall) {
            $Exclude += [regex]::Escape("$env:ChocolateyInstall\bin")
        }

        if ($Program) {
            # Search by command
            $cmdPaths = Get-Command $leaves -ErrorAction Ignore | Select-Object -ExpandProperty Path

            if ($cmdPaths) {
                $cmdResultPaths = @()
                [string[]]$scoopShimPaths = $cmdPaths |
                Where-Object {
                    (Split-Path $_ -Parent) -like '*scoop\shims' -and
                    (Split-Path $_ -Leaf) -match '\.exe$'
                }
                $GeneralCmdPaths = $cmdPaths | Where-Object { $scoopShimPaths -notcontains $_ }

                $cmdResultPaths += $GeneralCmdPaths

                if ($scoopShimPaths) {
                    if ((Get-Command -Name scoop -ErrorAction Ignore)) {
                        $scoopCmdPaths = Split-Path $scoopShimPaths -Leaf | ForEach-Object {
                            scoop which ($_ -replace '\.exe$', '')
                        }
                        $cmdResultPaths += $scoopCmdPaths
                    }
                    else {
                        $cmdResultPaths += $scoopShimPaths
                    }
                }

                if ((& $isCompleated -AddPath $cmdResultPaths)) {
                    return $resultPaths
                }
            }

            # Search by shortcut
            $lnkDirs = @(
                "$env:APPDATA\Microsoft\Windows\Start Menu"
                "C:\ProgramData\Microsoft\Windows\Start Menu"
            )

            $lnkResultPaths = Get-WULnkTarget -LiteralPath (Get-ChildItem -LiteralPath $lnkDirs -Recurse | Where-Object { $_.Extension -eq '.lnk' } | Select-Object -ExpandProperty FullName) -WarningAction Ignore

            if ((& $isCompleated -AddPath $lnkResultPaths)) {
                return $resultPaths
            }
        }

        # Search by es.exe
        if ((Get-Command -Name 'es.exe' -ErrorAction Ignore)) {
            $esResultPaths = $leaves | ForEach-Object {
                es.exe $_
            }

            if ((& $isCompleated -AddPath $esResultPaths)) {
                return $resultPaths
            }
        }
        else {
            Write-Warning "Cannot find command 'es.exe'."
        }

        return $resultPaths
    }
}