Modules/businessdev.ALbuild.RuntimePackages/Private/Get-BcRuntimeDependencyChain.ps1

function Get-BcRuntimeDependencyChain {
    <#
    .SYNOPSIS
        Returns the transitive catalogue dependencies of one product, in install order.
 
    .DESCRIPTION
        The factory installs a product's dependency chain, produces the runtime package, and then tears
        the whole chain down again before the next product. That teardown is what keeps products from
        influencing each other's runtime package inside a shared container, and it is only possible if
        the chain is known explicitly - both to install it in the right order and to remove it in the
        reverse one.
 
        The chain is transitive: a product that depends on 365 business API also needs whatever the API
        depends on. It excludes the product itself, and lists each dependency once even when several
        products in the chain require it.
 
        Only catalogue members appear here. Dependencies resolved from a feed (Microsoft apps,
        third-party apps) are installed by Install-BcContainerDependency from the manifest and are not
        this function's concern.
 
    .PARAMETER Product
        The full catalogue (typically already ordered by Get-BcRuntimeProductOrder).
 
    .PARAMETER Name
        The product whose chain is wanted.
 
    .OUTPUTS
        System.Object[] - catalogue entries, dependencies first.
    #>

    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [object[]] $Product,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name
    )

    $byName = [System.Collections.Specialized.OrderedDictionary]::new([System.StringComparer]::OrdinalIgnoreCase)
    foreach ($p in $Product) { $byName["$($p.Name)"] = $p }

    $chain = [System.Collections.Generic.List[object]]::new()
    $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)

    $walk = {
        param([string] $Current)

        $entry = $byName[$Current]
        if (-not $entry) { return }
        $dependsOn = @(Get-BcRuntimeProperty -InputObject $entry -Name 'DependsOn' -Default @())
        foreach ($dep in $dependsOn) {
            $depName = "$dep"
            if ([string]::IsNullOrWhiteSpace($depName) -or -not $byName.Contains($depName)) { continue }
            # Guard against a cycle: without this the walk would recurse until the stack gives out.
            if (-not $seen.Add($depName)) { continue }
            & $walk $depName                      # dependencies of the dependency come first
            $chain.Add($byName[$depName])
        }
    }

    [void]$seen.Add($Name)
    & $walk $Name

    return $chain.ToArray()
}