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