Modules/businessdev.ALbuild.Containers/Private/Get-BcContainerPowerShellExe.ps1

function Get-BcContainerPowerShellExe {
    <#
    .SYNOPSIS
        Resolves which in-container PowerShell host ('powershell' or 'pwsh') to run BC management
        commands under, for one container. Cached per container.
 
    .DESCRIPTION
        The BC management cmdlets are provided by .NET-Framework assemblies (loadable only by Windows
        PowerShell) up to ~BC27, and by .NET 8 assemblies (loadable only by PowerShell 7 / pwsh) from
        BC28. While a container still ships the legacy Microsoft.Dynamics.Nav.*.Management.dll we keep
        using 'powershell' - the long-proven path. Only when those are gone (a future .NET-only image)
        do we fall back to 'pwsh', which ships in modern BC containers and loads the .NET 8
        Microsoft.BusinessCentral.*.Management.dll. Override with the ALBUILD_CONTAINER_POWERSHELL
        environment variable (e.g. to force 'pwsh' for testing the .NET path on a BC28 image).
 
        NOTE (provisional): the pwsh branch is forward-looking scaffolding. On BC28 the legacy assemblies
        still ship, so this returns 'powershell' and the pwsh path is not yet exercised in production.
        pwsh + Microsoft.BusinessCentral.*.Management.dll has been verified to work for the management
        cmdlets directly, but the full encoded-command round-trip under the container's PowerShell 7 needs
        validation against a real .NET-only BC image before this can be relied on - see the design notes.
 
    .PARAMETER Name
        Container name.
 
    .PARAMETER DockerExecutable
        The Docker executable to use (default 'docker').
 
    .OUTPUTS
        System.String - 'powershell' or 'pwsh'.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name,
        [string] $DockerExecutable = 'docker'
    )

    if (-not [string]::IsNullOrWhiteSpace($env:ALBUILD_CONTAINER_POWERSHELL)) {
        return $env:ALBUILD_CONTAINER_POWERSHELL
    }

    # StrictMode-safe lazy init (the script-scope var may not exist yet).
    if (-not (Test-Path 'variable:script:BcContainerHostCache')) { $script:BcContainerHostCache = @{} }
    if ($script:BcContainerHostCache.ContainsKey($Name)) { return $script:BcContainerHostCache[$Name] }

    $exe = 'powershell'
    try {
        # Probe (with Windows PowerShell, which is always present) whether the legacy .NET-Framework NAV
        # management DLLs still ship. If so, stay on 'powershell'. Otherwise, prefer 'pwsh' when present.
        $legacy = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru -Arguments @(
            'exec', $Name, 'powershell', '-NoProfile', '-Command',
            "[bool]@(Get-ChildItem 'C:\Program Files\Microsoft Dynamics NAV' -Recurse -Filter 'Microsoft.Dynamics.Nav.*Management.dll' -ErrorAction SilentlyContinue).Count")
        $hasLegacy = $legacy.Success -and ("$($legacy.StdOut)".Trim() -eq 'True')
        if (-not $hasLegacy) {
            $pwsh = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru -SuccessExitCodes @(0, 1) -Arguments @(
                'exec', $Name, 'cmd', '/c', 'where pwsh')
            if ($pwsh.ExitCode -eq 0 -and -not [string]::IsNullOrWhiteSpace($pwsh.StdOut)) { $exe = 'pwsh' }
        }
    }
    catch {
        $exe = 'powershell'
    }

    $script:BcContainerHostCache[$Name] = $exe
    return $exe
}