Modules/businessdev.ALbuild.OnPrem/Private/Import-BcManagementShell.ps1

function Import-BcManagementShell {
    <#
    .SYNOPSIS
        Loads the Business Central administration shell (Publish-NAVApp / Sync-NAVApp /
        Install-NAVApp / Get-NAVAppInfo / ...) into the current session.
 
    .DESCRIPTION
        The on-prem cmdlets are provided by the BC server's management assemblies, which are NOT on
        PSModulePath and are therefore not auto-loaded. Without this, an on-prem publish fails with
        "The term 'Publish-NAVApp' is not recognized". Mirrors the AL.build V1 ReleaseOnPrem behaviour:
        if the shell is already present it is reused; otherwise the management assemblies are located
        under the BC install tree and imported (modern 'Microsoft.BusinessCentral.*' first, then the
        legacy 'Microsoft.Dynamics.Nav.*' names for older versions).
 
        Progress is logged in detail (every search root, every assembly imported and from where) so a
        failure on a customer agent is diagnosable from the pipeline log alone.
 
    .PARAMETER SentinelCommand
        The command whose presence proves the shell is loaded. Default 'Publish-NAVApp' (the cmdlet the
        on-prem publish actually needs).
 
    .PARAMETER SearchRoot
        Glob root(s) under which to search for the management assemblies.
 
    .OUTPUTS
        None. Throws a clear, actionable error listing the searched locations if the shell cannot be loaded.
    #>

    [CmdletBinding()]
    param(
        [string] $SentinelCommand = 'Publish-NAVApp',
        [string[]] $SearchRoot = @(
            'C:\Program Files\Microsoft Dynamics*\*\Service\',
            'C:\Program Files (x86)\Microsoft Dynamics*\*\Service\'
        )
    )

    if (Get-Command -Name $SentinelCommand -ErrorAction SilentlyContinue) {
        Write-ALbuildLog "BC management shell already loaded ('$SentinelCommand' available); reusing it."
        return
    }

    Write-ALbuildLog "BC management shell not loaded ('$SentinelCommand' missing); locating the management assemblies..."

    # Modern (BC15+) assemblies first, then the legacy NAV names for older on-prem versions. Each set
    # provides the server (…Management) + app (…Apps.Management) cmdlets we call.
    $moduleSets = @(
        @('Microsoft.BusinessCentral.Management.dll', 'Microsoft.BusinessCentral.Apps.Management.dll'),
        @('Microsoft.Dynamics.Nav.Management.dll', 'Microsoft.Dynamics.Nav.Apps.Management.dll')
    )

    $searched = [System.Collections.Generic.List[string]]::new()
    $importedAny = $false
    foreach ($set in $moduleSets) {
        foreach ($dll in $set) {
            foreach ($root in $SearchRoot) {
                # String-concatenate (roots end in a separator) rather than Join-Path: Join-Path
                # resolves the 'C:' qualifier and throws "drive 'C' does not exist" on non-Windows
                # agents (the module is loaded/tested on the Linux CI leg, though it only runs for real
                # on a Windows BC server).
                $searched.Add($root + $dll)
                $found = @()
                # NOTE: do NOT add -File here. With a wildcard '...\*\Service\' path + -Recurse, -File makes
                # Get-ChildItem return nothing (PowerShell quirk), so the assemblies are never found - which
                # is exactly why V1/the reference installer omit it. Filter out directories in the loop instead.
                try { $found = @(Get-ChildItem -Path $root -Filter $dll -Recurse -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer }) }
                catch { $found = @() }   # a Windows-only root throws 'drive not found' off Windows; treat as no match
                foreach ($f in $found) {
                    Write-ALbuildLog " importing '$($f.Name)' from '$($f.Directory.FullName)'."
                    try {
                        Import-Module -Name $f.FullName -Force -Global -DisableNameChecking -ErrorAction Stop
                        $importedAny = $true
                    }
                    catch {
                        Write-ALbuildLog -Level Warning " failed to import '$($f.FullName)': $($_.Exception.Message)."
                    }
                }
            }
        }
        # Stop at the first assembly set that yields the cmdlet we need.
        if (Get-Command -Name $SentinelCommand -ErrorAction SilentlyContinue) { break }
    }

    if (-not (Get-Command -Name $SentinelCommand -ErrorAction SilentlyContinue)) {
        $hint = if ($importedAny) {
            "Assemblies were found and imported but '$SentinelCommand' is still unavailable - the installed BC version may use different module names."
        }
        else {
            'No BC management assemblies were found under the search roots - the BC server (or its management tools) does not appear to be installed on this agent.'
        }
        throw ("Could not load the Business Central management shell. $hint" + [Environment]::NewLine +
            "Searched: $(($searched | Select-Object -Unique) -join '; ')")
    }

    Write-ALbuildLog -Level Success "BC management shell loaded ('$SentinelCommand' now available)."
}