Modules/businessdev.ALbuild.Apps/Private/Get-BcContainerSymbolFolder.ps1

function Get-BcContainerSymbolFolder {
    <#
    .SYNOPSIS
        Selects the in-container first-party symbol (package cache) folders for a Container-engine compile.
 
    .DESCRIPTION
        Runs INSIDE the build container (its source is injected into the container command by
        Invoke-BcCompiler, because the container has no access to the module). Given the container's
        artifact cache and platform install, it returns the folders that hold the compiled Microsoft
        first-party symbol packages (Application / Base Application / System Application / Business
        Foundation, the test toolkit, ...) that alc needs, coping with every artifact layout:
 
          * SANDBOX: the apps sit TOP-LEVEL in '<country>\Extensions' and '<country>\Applications.<country>'.
          * ON-PREM: there is no 'Extensions'; the full country-specific first-party set is NESTED as
            '<country>\Applications\<App>\Source\Microsoft_<App>.app', and the file name carries NO version
            - so matching folder NAMES alone misses it. Every folder that actually holds '*.app' is taken.
 
        Two hazards are handled:
          * Version scoping - a shared artifact cache (self-hosted agents reuse one C:\dl / bcartifacts.cache)
            can hold several BC versions; feeding alc a different version's apps makes it reject ALL of them
            (AL1023). The search is scoped to the container's own platform major (derived from the installed
            'Microsoft Dynamics NAV\<nnn>' folder, e.g. '260' -> '26.').
          * Localization vs. W1 - on-prem ships both a localized '<country>\Applications' tree and a
            'platform\Applications' (W1) tree. Feeding both duplicates app ids / lets the W1 copy shadow the
            localization (e.g. the German 'Delivery Reminder'), so '\platform\' folders are dropped whenever a
            country tree is present. The platform 'System.app' is added separately (it is not a country app).
 
        A widened fallback covers images/caches that keep the base symbol packages outside the artifact.
 
    .OUTPUTS
        PSCustomObject with SymbolFolders (string[] to pass as /packagecachepath), VersionPrefix,
        ApplicationFound (folders that contain a 'Microsoft_Application*.app') and Widened (fallback folders).
 
    .NOTES
        All roots are parameters (defaulting to the real container paths) so the selection rule can be
        unit-tested against fake sandbox/on-prem trees without a container.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [string]   $NavProgramFiles = 'C:\Program Files\Microsoft Dynamics NAV',
        [string]   $DlGlob          = 'C:\dl\*\*',
        [string]   $ContainerRoot   = 'C:\',
        [string]   $SystemAppGlob   = 'C:\dl\*\*\platform\ModernDev',
        [string[]] $WidenRoot       = @('C:\Applications', 'C:\dl', 'C:\build', 'C:\run', 'C:\bcartifacts.cache', 'C:\Program Files\Microsoft Dynamics NAV', 'C:\ProgramData\Microsoft\Microsoft Dynamics NAV'),
        [string]   $VersionPrefix
    )

    # Target platform major, e.g. '260' -> '26.'. Scopes the artifact search to the container's own version.
    if (-not $PSBoundParameters.ContainsKey('VersionPrefix')) {
        $navDirs = @(Get-ChildItem $NavProgramFiles -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d+$' })
        $navName = ($navDirs | Sort-Object { [int]$_.Name } -Descending | Select-Object -First 1).Name
        $VersionPrefix = if ($navName) { "$([int]$navName.Substring(0, $navName.Length - 1))." } else { '' }
    }

    $cand = New-Object System.Collections.Generic.List[string]
    $artRoots = @(Get-ChildItem $DlGlob -Directory -ErrorAction SilentlyContinue)   # C:\dl\<type>\<ver>
    if ($VersionPrefix) { $artRoots = @($artRoots | Where-Object { $_.Name -like "$VersionPrefix*" }) }
    foreach ($ar in $artRoots) {
        $withApps = @(Get-ChildItem $ar.FullName -Recurse -Directory -ErrorAction SilentlyContinue |
                Where-Object { Get-ChildItem -LiteralPath $_.FullName -Filter '*.app' -File -ErrorAction SilentlyContinue | Select-Object -First 1 })
        $nonPlat = @($withApps | Where-Object { $_.FullName -notmatch '(?i)[\\/]platform[\\/]' })
        $use = if ($nonPlat.Count -gt 0) { $nonPlat } else { $withApps }
        foreach ($d in $use) { $cand.Add($d.FullName) }
    }
    # The container's own application folders (some images keep compiled first-party apps here).
    Get-ChildItem $ContainerRoot -Directory -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -match '^Applications(\..+)?$' } |
        ForEach-Object { $cand.Add($_.FullName) }
    # The platform 'System' symbol package ships as System.app under the AL Development Environment.
    $sysApp = Get-ChildItem $NavProgramFiles -Recurse -Filter 'System.app' -File -ErrorAction SilentlyContinue | Select-Object -First 1
    if (-not $sysApp) { $sysApp = Get-ChildItem $SystemAppGlob -Recurse -Filter 'System.app' -File -ErrorAction SilentlyContinue | Select-Object -First 1 }
    if ($sysApp) { $cand.Add($sysApp.DirectoryName) }

    # Keep only folders that actually contain .app packages (drops app-source folders), de-duplicated.
    $symDirs = @($cand | Select-Object -Unique | Where-Object { $_ -and (Get-ChildItem -LiteralPath $_ -Filter '*.app' -File -ErrorAction SilentlyContinue | Select-Object -First 1) })
    # 'Microsoft_Application*.app' matches both the sandbox '_<version>' and on-prem unversioned name.
    $appLoc = @($symDirs | Where-Object { Get-ChildItem -LiteralPath $_ -Filter 'Microsoft_Application*.app' -File -ErrorAction SilentlyContinue })

    # If the scoped search did not find 'Application', widen it across the likely container roots for the
    # base symbol packages of the TARGET version (the version filter keeps a newer cached version's apps out).
    $widened = @()
    if (-not $appLoc -and $VersionPrefix) {
        $vEsc = [regex]::Escape($VersionPrefix)
        foreach ($pr in $WidenRoot) {
            if (-not (Test-Path -LiteralPath $pr)) { continue }
            Get-ChildItem -LiteralPath $pr -Recurse -File -Filter '*.app' -ErrorAction SilentlyContinue |
                Where-Object { ($_.Name -like 'Microsoft_Application_*' -or $_.Name -like 'Microsoft_Base Application_*' -or $_.Name -like 'Microsoft_System Application_*') -and $_.Name -match "_$vEsc" } |
                ForEach-Object { $widened += $_.DirectoryName }
        }
        $widened = @($widened | Select-Object -Unique)
        foreach ($w in $widened) { if ($symDirs -notcontains $w) { $symDirs += $w } }
        $appLoc = @($symDirs | Where-Object { Get-ChildItem -LiteralPath $_ -Filter 'Microsoft_Application*.app' -File -ErrorAction SilentlyContinue })
    }

    return [PSCustomObject]@{
        SymbolFolders    = @($symDirs)
        VersionPrefix    = $VersionPrefix
        ApplicationFound = @($appLoc)
        Widened          = @($widened)
    }
}