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 } |