Modules/businessdev.ALbuild.Containers/Public/Get-BcContainerAppFileInfo.ps1

function Get-BcContainerAppFileInfo {
    <#
    .SYNOPSIS
        Reads the real identity (app id / name / publisher / version) of one or more .app files by
        asking the build container, for packages the host cannot read itself.
 
    .DESCRIPTION
        A committed **encrypted BC runtime package** has no host-readable ZIP payload, so its app id can
        not be extracted on the agent (Expand-BcAppFile throws, and the id is not recoverable from the
        file header). The container, however, decrypts the manifest: 'Get-NAVAppInfo -Path' returns the
        real identity. This copies the .app file(s) into the container's C:\run\my share and reads them
        there, so a locally-served runtime dependency can be pinned by its ACTUAL app id (matching the
        consuming app.json 'dependencies') instead of relying on a '{Publisher}_{Name}_{Version}.app'
        file-name convention.
 
    .PARAMETER ContainerName
        The build container to read through (its C:\run\my share is used to hand the files across).
 
    .PARAMETER Path
        One or more .app file paths on the host.
 
    .PARAMETER DockerExecutable
        The Docker executable to use (default 'docker').
 
    .OUTPUTS
        For each readable file, a PSCustomObject with Id, Name, Publisher, Version and File (the original
        host path). Files the container cannot read are omitted.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [string] $ContainerName,
        [Parameter(Mandatory)] [string[]] $Path,
        [string] $DockerExecutable = 'docker'
    )

    $share = Get-BcContainerHostShare -Name $ContainerName
    $leaf = 'appinfo-' + [guid]::NewGuid().ToString('N').Substring(0, 8)
    $hostWork = Join-Path $share $leaf
    New-Item -ItemType Directory -Force -Path $hostWork | Out-Null

    $results = [System.Collections.Generic.List[object]]::new()
    try {
        # Stage under ASCII index names ('0.app', '1.app', ...) and map back by index: a source file name
        # can contain non-ASCII characters (umlauts, accents, ...) that do not round-trip cleanly through
        # docker exec / JSON, which would break a name-based match. Get-NAVAppInfo reads the app
        # id/name/version from the manifest, not the file name, so the staged name is irrelevant.
        $byIndex = @{}
        $idx = 0
        foreach ($p in $Path) {
            if (-not (Test-Path -LiteralPath $p)) { continue }
            Copy-Item -LiteralPath $p -Destination (Join-Path $hostWork "$idx.app") -Force
            $byIndex["$idx"] = (Resolve-Path -LiteralPath $p).Path
            $idx++
        }
        if ($byIndex.Count -eq 0) { return @() }

        # In-container reader: BC's Apps management module targets .NET 8, so it must run under pwsh
        # (Windows PowerShell 5.1 in the container cannot load it). Emit one JSON line per readable app.
        $reader = @'
$ErrorActionPreference = 'Stop'
$mod = Get-ChildItem 'C:\Program Files\Microsoft Dynamics NAV\*\Service\Admin\Microsoft.BusinessCentral.Apps.Management.psd1' -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $mod) { throw 'BC Apps management module not found in the container.' }
Import-Module $mod.FullName
$out = foreach ($f in (Get-ChildItem '__CWORK__' -Filter *.app -File)) {
    try {
        $i = Get-NAVAppInfo -Path $f.FullName -ErrorAction Stop
        [PSCustomObject]@{ Leaf = $f.Name; Id = "$($i.AppId)"; Name = "$($i.Name)"; Publisher = "$($i.Publisher)"; Version = "$($i.Version)" }
    }
    catch { }
}
@($out) | ConvertTo-Json -Compress -Depth 3
'@
 -replace '__CWORK__', ('C:\run\my\' + $leaf)
        $readerHost = Join-Path $hostWork 'read.ps1'
        Set-Content -LiteralPath $readerHost -Value $reader -Encoding UTF8

        $json = & $DockerExecutable exec $ContainerName pwsh -NoProfile -ExecutionPolicy Bypass -File "C:/run/my/$leaf/read.ps1" 2>$null | Out-String
        $parsed = $null
        try { if ($json.Trim()) { $parsed = $json | ConvertFrom-Json } } catch { Write-Verbose "Could not parse container app-info JSON: $($_.Exception.Message)" }

        foreach ($e in @($parsed)) {
            if (-not $e -or -not $e.Id) { continue }
            $orig = $byIndex[[System.IO.Path]::GetFileNameWithoutExtension("$($e.Leaf)")]
            if (-not $orig) { continue }
            $results.Add([PSCustomObject]@{ Id = "$($e.Id)"; Name = "$($e.Name)"; Publisher = "$($e.Publisher)"; Version = "$($e.Version)"; File = $orig })
        }
    }
    finally { Remove-Item -LiteralPath $hostWork -Recurse -Force -ErrorAction SilentlyContinue }

    return $results.ToArray()
}