Functions/AppManagement/Get-AppDependencyOrder.ps1

function Get-AppDependencyOrder {
    [CmdletBinding(DefaultParameterSetName='FilePath')]
    param(            
        [Parameter(Mandatory=$true, ParameterSetName = "FilePath")]
        [string[]] $Path,
        [Parameter(Mandatory=$true, ParameterSetName = "AppObject")]
        [object[]] $Apps,
        [Parameter(Mandatory=$true, ParameterSetName = "AppObject")]
        [string] $ServerInstance
    )

    # Create the list of extensions based on the used parameterSet.
    if($PSCmdlet.ParameterSetName -eq 'AppObject'){
        $AllApps = @()
        foreach ($App in $Apps) {
            $App = Get-NAVAppInfo -ServerInstance $ServerInstance `
                                  -Id $App.AppId.Value.Guid `
                                  -Version $App.Version
            $App | Add-Member NoteProperty -Name Path -Value 'unknown'
            $App | Add-Member NoteProperty -Name Length -Value 'unknown'
            $App | Add-Member NoteProperty -Name TotalDependencies `
                                           -Value $App.Dependencies.Count
            $AllApps += $App
        }
    }

    if($PSCmdlet.ParameterSetName -eq 'FilePath'){
        
        $AllAppFiles = @()
        $Path | ForEach-Object {
            $AllAppFiles += Get-ChildItem -Path $_ -Recurse -Filter "*.app"
        }

        $AllApps = @()
        foreach ($AppFile in $AllAppFiles) {
            $App = Get-NAVAppInfo -Path $AppFile.FullName
            $App | Add-Member NoteProperty -Name Path -Value $AppFile.FullName
            $App | Add-Member NoteProperty -Name Length -Value $AppFile.Length
            $App | Add-Member NoteProperty -Name TotalDependencies `
                                           -Value $App.Dependencies.Count
            $AllApps += $App
        }

        
        # Add application guid to extensions dependency list if not pressent.
        # This resolves the dynamic dependencies between the base extensions and
        # the extension on top of the base application.
        function Get-ApplicationDependencies($App, $AllApps){
            $guids = @()
            $guids += $App.AppId.Value.Guid
            foreach ($Dependency in $App.Dependencies) {
                $DependentApp = $AllApps | Where-Object { 
                    $Dependency.AppId.Guid -eq $_.AppId.Value.Guid }
                $guids += Get-ApplicationDependencies $DependentApp $AllApps
            }
            return $guids
        }
        $application = $AllApps | Where-Object Name -eq 'Application'
        $applicationGuid = $application.AppId.Value.Guid
        $baseLayerAppGuids = Get-ApplicationDependencies $application $allApps

        # Skip step if the application extension is not included in the installation.
        if($applicationGuid){
            foreach ($App in $AllApps) {
                
                # Skip applications that already have a dependency on the application.
                $depOnApp = $App.Dependencies | Where-Object Name -eq 'Application'
                
                if($depOnApp -and $depOnApp.AppId -eq '00000000-0000-0000-0000-000000000000'){
                    ($App.Dependencies | Where-Object Name -eq 'Application').AppId = $applicationGuid 
                }
                if($depOnApp){
                    continue
                }

                # Prevent a circulair dependency.
                if($App.AppId.Value.Guid -in $baseLayerAppGuids){
                    continue
                }
                
                # Add application dependency
                $App.Dependencies.Add( @{
                    'AppId' = $applicationGuid
                    'Name' = 'Application'
                })
            }
        }
    } # end $PSCmdlet.ParameterSetName -eq 'FilePath'

    # Check on AppId Uniqueness.
    $Result = Get-BCAppidUnique ($AllApps)
    if ($Result) {
        Write-Error ($Result | Out-String)
    }

    # Add poperties for calculating the directed acyclic graph (DAG).
    foreach ($App in $AllApps) {
        $App | Add-Member NoteProperty -Name InDegree -Value 0
        $App | Add-Member NoteProperty -Name Visited -Value $false
    }

    # Calculate In-Degree for each App in the DAG.
    foreach ($App in $AllApps) {
        foreach ($Dependency in $App.Dependencies) {
            
            $DependentApp = $AllApps | Where-Object { 
                $Dependency.AppId.Guid -eq $_.AppId.Value.Guid}

            if($DependentApp){
                $DependentApp.InDegree++
            }
        }
    }

    $AllApps = $AllApps | Sort-Object -Property TotalDependencies -Descending

    # Topological sort the apps.
    $Queue = New-Object 'System.Collections.Generic.Queue[PSObject]'
    $AllApps | Where-Object { $_.InDegree -eq 0 } | ForEach-Object { $Queue.Enqueue($_) }
    $BuildOrder = @()

    while ($Queue.Count -gt 0) {
        $CurrentApp = $Queue.Dequeue()

        # Mark the current App as visited
        $CurrentApp.Visited = $true

        # Add the current App to the build order
        $BuildOrder += $CurrentApp

        # Decrease the in-degree of all dependent Apps by 1
        foreach ($Dependency in $CurrentApp.Dependencies) {

            $DependentApp = $AllApps | Where-Object { 
                $Dependency.AppId.Guid -eq $_.AppId.Value.Guid}
            
            if($DependentApp){
                $DependentApp.InDegree--
            }

            # Enqueue the dependent App if its in-degree becomes 0 and it has not been visited
            if ($DependentApp.InDegree -eq 0 -and -not $DependentApp.Visited) {
                $Queue.Enqueue($DependentApp)
            }
        }
    }

    # Check for circular dependencies
    if ($BuildOrder.Count -ne $AllApps.Count) {
        Write-Error "Circular dependencies detected. Please resolve the dependencies to proceed."
        return
    }

    $Exclude = @('InDegree', 'Visited', 'TotalDependencies')
    $BuildOrder = $BuildOrder | Select-Object -Property * -ExcludeProperty $Exclude

    if($BuildOrder.Count -gt 1){
        [array]::Reverse($BuildOrder)
    }

    return $BuildOrder
}

Export-ModuleMember -Function Get-AppDependencyOrder

function Get-BCAppidUnique() {
    param(
        [Parameter(Mandatory=$true)]
        [PSObject] $Apps
    )
    
   # $Appslist is sorted on AppId
    $AppsList = $Apps | Sort-Object Appid

    $Result = @()
    for ($i=1; $i -lt $AppsList.Count; $i++) {
        if ($AppsList[$i -1].AppId.Value.Guid -eq $AppsList[$i].AppId.Value.Guid) {
            # Found Duplicate Appid
            $Result += 'Found Duplicate Appid in app {0}:' -f $AppsList[$i].Name
            $Result += '{0}[{1}]' -f $AppsList[$i-1].Name, $AppsList[$i-1].AppId
            $Result += '{0}[{1}]' -f $AppsList[$i].Name, $AppsList[$i].AppId
        }
    }

    return $Result
}