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() } |