Modules/businessdev.ALbuild.Containers/Private/ConvertTo-BcEncodedCommand.ps1

function ConvertTo-BcEncodedCommand {
    <#
    .SYNOPSIS
        Builds a Base64 -EncodedCommand payload for running a script block inside a container.
    .DESCRIPTION
        Internal, pure helper. Prepends a strict error preference, an import of the Business Central
        management module (the 'docker exec ... powershell -NoProfile' session does not load it, so
        NAV cmdlets such as Publish-NAVApp would otherwise be unrecognised) and optional variable
        assignments (passed as JSON to survive the process boundary), then UTF-16LE/Base64 encodes
        the result for 'powershell -EncodedCommand'. Pure (no I/O), so it is unit-testable.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [scriptblock] $ScriptBlock,

        [hashtable] $Variables = @{},

        # Return the assembled script text instead of the Base64 payload. The caller can then decide
        # whether to pass it via -EncodedCommand or - for scripts too long for the command line - via
        # a staged -File.
        [switch] $AsText
    )

    # Make the BC management cmdlets (Get-NAVAppInfo, Publish-/Sync-/Install-NAVApp, Get-NAVServerInstance,
    # ...) available inside the container; the 'docker exec' session does not auto-load them. Both the
    # assembly NAME/location and the PowerShell edition that can load it vary by BC version:
    # Windows PowerShell (.NET Framework) -> Microsoft.Dynamics.Nav.*.Management.dll (BC <= 28)
    # PowerShell 7 (.NET) -> Microsoft.BusinessCentral.*.Management.dll (BC 24+, .NET 8)
    # BC29 dropped the Windows PowerShell set entirely, so there the caller runs us under pwsh (see
    # Get-BcContainerPowerShellExe). Pick the assemblies matching the running edition (a .NET 8 DLL won't
    # load in Windows PowerShell and vice versa), search recursively (location moved across versions), and
    # import each. Tolerant (no throw) so commands needing no NAV cmdlets still run.
    #
    # Do NOT guard this with 'Get-Command <cmdlet-not-yet-loaded>'. Inside a BC container, pwsh dies on
    # that - exit 255, no stdout, no stderr, not catchable by try/catch - because command auto-discovery
    # scans C:\Windows\system32\WindowsPowerShell\v1.0\Modules, hits Desktop-edition-only modules and
    # triggers the Windows PowerShell Compatibility feature, which cannot start in the container. Testing
    # 'Get-Module' instead only inspects already-loaded modules, so it never triggers discovery. (This
    # mirrors Microsoft's own NavAdminTool.ps1, which also probes with Get-Module.)
    $importNavManagement = @'
$__navDllNames = if ($PSVersionTable.PSEdition -eq 'Core') {
    @('Microsoft.BusinessCentral.Apps.Management.dll', 'Microsoft.BusinessCentral.Management.dll',
      'Microsoft.Dynamics.Nav.Apps.Management.dll', 'Microsoft.Dynamics.Nav.Management.dll')
} else {
    @('Microsoft.Dynamics.Nav.Apps.Management.dll', 'Microsoft.Dynamics.Nav.Management.dll')
}
foreach ($__navDll in $__navDllNames) {
    if (Get-Module -Name ([System.IO.Path]::GetFileNameWithoutExtension($__navDll))) { continue }
    foreach ($__navFile in @(Get-ChildItem 'C:\Program Files\Microsoft Dynamics NAV' -Recurse -Filter $__navDll -ErrorAction SilentlyContinue)) {
        try { Import-Module $__navFile.FullName -DisableNameChecking -ErrorAction Stop } catch { }
    }
}
'@


    $sb = [System.Text.StringBuilder]::new()
    [void]$sb.AppendLine('$ErrorActionPreference = ''Stop''')
    [void]$sb.AppendLine($importNavManagement)
    foreach ($key in $Variables.Keys) {
        $json = ($Variables[$key] | ConvertTo-Json -Compress -Depth 20)
        $escaped = $json -replace "'", "''"
        [void]$sb.AppendLine("`$$key = '$escaped' | ConvertFrom-Json")
    }
    [void]$sb.AppendLine($ScriptBlock.ToString())

    if ($AsText) { return $sb.ToString() }
    $bytes = [System.Text.Encoding]::Unicode.GetBytes($sb.ToString())
    return [System.Convert]::ToBase64String($bytes)
}