Public/Resolve-EnvkExecutablePath.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    Resolves and validates an executable path across filesystem, PATH, and UWP types.
 
.DESCRIPTION
    Auto-detects the path type from the string content and validates accordingly:
 
      - shell:AppsFolder\<AUMID> — UWP (delegated to Resolve-UwpPath via Shell.Application COM)
      - Contains slashes (/ or \) — Filesystem path; environment variables are expanded first
      - Plain name (no slashes) — PATH executable lookup via Get-Command
 
    Environment variable tokens (e.g. %LOCALAPPDATA%) are expanded using
    [System.Environment]::ExpandEnvironmentVariables before filesystem and PATH detection.
    The UWP branch is tested before expansion because shell:AppsFolder paths require no
    expansion and the colon/backslash would otherwise route to the filesystem branch.
 
    Returns a PSCustomObject with three properties:
      Valid — [bool] Whether the path was successfully resolved
      Reason — [string] Human-readable explanation of the result
      ResolvedPath — [string] The expanded/resolved path, or $null if not found
 
.PARAMETER Path
    The path string to resolve. May be a filesystem path (with or without environment
    variable tokens), a PATH-available executable name, or a shell:AppsFolder AUMID.
 
.OUTPUTS
    PSCustomObject with properties: Valid (bool), Reason (string), ResolvedPath (string).
 
.EXAMPLE
    # Filesystem path with environment variable
    Resolve-EnvkExecutablePath -Path '%PROGRAMFILES%\Mozilla Firefox\firefox.exe'
 
.EXAMPLE
    # PATH executable
    Resolve-EnvkExecutablePath -Path 'explorer.exe'
 
.EXAMPLE
    # UWP AUMID
    Resolve-EnvkExecutablePath -Path 'shell:AppsFolder\MSTeams_8wekyb3d8bbwe!MSTeams'
 
.NOTES
    Author: Aaron AlAnsari
    Created: 2026-02-25
 
    Detection order (CRITICAL — ordering affects correctness):
      1. UWP check BEFORE env-var expansion (shell:AppsFolder must not be expanded)
      2. Env-var expansion BEFORE slash check (%LOCALAPPDATA%\app.exe has slashes after expansion)
      3. Slash check BEFORE PATH lookup (filesystem paths always contain slashes)
#>


function Resolve-EnvkExecutablePath {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter()]
        [AllowEmptyString()]
        [string]$Path = ''
    )

    # Guard: null/empty input.
    if ([string]::IsNullOrWhiteSpace($Path)) {
        return [PSCustomObject]@{
            Valid        = $false
            Reason       = 'Path is null or empty.'
            ResolvedPath = $null
        }
    }

    # Branch 1: UWP shell:AppsFolder AUMID — check BEFORE env-var expansion.
    if ($Path -like 'shell:AppsFolder\*') {
        $aumid = $Path.Substring('shell:AppsFolder\'.Length)
        Write-EnvkLog -Level 'DEBUG' -Message "Resolve-EnvkExecutablePath: detected UWP path — $aumid"
        return Resolve-UwpPath -Aumid $aumid -OriginalPath $Path
    }

    # Expand environment variables (e.g. %WINDIR%, %LOCALAPPDATA%) — must happen BEFORE
    # the slash check so that tokens like %LOCALAPPDATA%\app.exe route to the filesystem branch.
    $expanded = [System.Environment]::ExpandEnvironmentVariables($Path)

    # Branch 2: Filesystem path — contains slashes after expansion.
    if ($expanded -match '[/\\]') {
        Write-EnvkLog -Level 'DEBUG' -Message "Resolve-EnvkExecutablePath: detected filesystem path — $expanded"
        if (Test-Path -Path $expanded -PathType Leaf) {
            Write-EnvkLog -Level 'DEBUG' -Message "Resolve-EnvkExecutablePath: filesystem path valid — $expanded"
            return [PSCustomObject]@{
                Valid        = $true
                Reason       = 'Filesystem path exists.'
                ResolvedPath = $expanded
            }
        }
        else {
            Write-EnvkLog -Level 'DEBUG' -Message "Resolve-EnvkExecutablePath: filesystem path not found — $expanded"
            return [PSCustomObject]@{
                Valid        = $false
                Reason       = "Path not found: '$expanded'."
                ResolvedPath = $expanded
            }
        }
    }

    # Branch 3: PATH executable — plain name with no slashes.
    Write-EnvkLog -Level 'DEBUG' -Message "Resolve-EnvkExecutablePath: detected PATH executable — $expanded"
    $cmd = Get-Command $expanded -ErrorAction SilentlyContinue
    if ($null -ne $cmd) {
        Write-EnvkLog -Level 'DEBUG' -Message "Resolve-EnvkExecutablePath: PATH executable found — $($cmd.Source)"
        return [PSCustomObject]@{
            Valid        = $true
            Reason       = 'Executable found in PATH.'
            ResolvedPath = $cmd.Source
        }
    }
    else {
        Write-EnvkLog -Level 'DEBUG' -Message "Resolve-EnvkExecutablePath: PATH executable not found — $expanded"
        return [PSCustomObject]@{
            Valid        = $false
            Reason       = "Executable '$expanded' not found in PATH."
            ResolvedPath = $null
        }
    }
}