Export/Public/Get-AppDependencyInfo.ps1

function Get-AppDependencyInfo {
    [CmdletBinding()]
    <#
    .SYNOPSIS
        ...
    .DESCRIPTION
        ...
    #>

    param(
        [parameter(Mandatory = $false)]
        [string]
        $ServerInstance,
        [parameter(Mandatory = $false)]
        [string]
        $Tenant,
        [parameter(Mandatory = $false)]
        [string]
        $AppPublisher,
        [parameter(Mandatory = $true)]
        [string]
        $AppName,
        [parameter(Mandatory = $false)]
        [switch]
        $IncludeUninstalled = $false,
        [parameter(Mandatory = $false)]
        [switch]
        $ReverseLookup = $false
    )    
    begin {
        if (-not($ServerInstance)) {
            $ServerInstance = Get-D365BCServerInstanceName
        }
        if (-not($Tenant)) {
            $Tenant = Get-D365BCInstanceTenant
        }
    }
    process {
        function Get-AppsWithDependencies {
            [CmdletBinding()]
            param(
                [parameter(Mandatory = $true)]
                [string]
                $ServerInstance,
                [parameter(Mandatory = $false)]
                [string]
                $Tenant,
                [parameter(Mandatory = $false)]
                [switch]
                $IncludeUninstalled = $false
            )
            function Expand-DependentApps {
                [CmdletBinding()]
                param(
                    [parameter(Mandatory = $true)]
                    [PSObject]
                    $Apps
                )
                function Get-DependentAppsForApp {
                    [CmdletBinding()]
                    param(        
                        [parameter(Mandatory = $true)]
                        [PSObject]
                        $PopulatedApps,
                        [parameter(Mandatory = $false)]
                        [string]
                        $AppPublisher,
                        [parameter(Mandatory = $true)]
                        [string]
                        $AppName
                    )    
                    if ($AppPublisher) {
                        $App = $PopulatedApps | Where-Object { ($_.Publisher -eq $AppPublisher) -and ($_.Name -eq $AppName) } | Select-Object -First 1
                    }
                    else {
                        $App = $PopulatedApps | Where-Object { ($_.Name -eq $AppName) } | Select-Object -First 1
                    }
                    $mainApp = [PSCustomObject]@{
                        AppId             = $App.AppId
                        Name              = $App.Name
                        Version           = $App.Version
                        Publisher         = $App.Publisher
                        NoOfDependencies  = $App.NoOfDependencies
                        NoOfDependentApps = $App.NoOfDependencies
                        Dependencies      = $App.Dependencies
                        DependentApps     = @()
                    }
                    foreach ($PopulatedApp in $PopulatedApps) {
                        if ($AppPublisher) {
                            $hasDependency = $PopulatedApp.Dependencies | Where-Object { ($_.Publisher -eq $AppPublisher) -and ($_.Name -eq $AppName) }
                        }
                        else {
                            $hasDependency = $PopulatedApp.Dependencies | Where-Object { $_.Name -eq $App.Name }
                        }   
                        if ($hasDependency) {
                            $mainApp.DependentApps += $PopulatedApp
                        }
                    }
                    $mainApp.NoOfDependentApps = $mainApp.DependentApps.Count
                    Write-Verbose "$($mainApp.Name) has $($mainApp.DependentApps.Count) dependent app(s)"
                    foreach ($app in $mainApp.DependentApps) {
                        Write-Verbose "[-] $($app.Name)"
                    }
                    $mainApp
                }
                for ($i = 0; $i -lt $Apps.Length; $i++) {
                    $Apps[$i] = Get-DependentAppsForApp -PopulatedApps $Apps -AppName $Apps[$i].Name
                }
                $Apps
            }
            function Get-AppWithPopulatedDependencies {
                [CmdletBinding()]
                param(
                    [parameter(Mandatory = $true)]
                    [string]
                    $ServerInstance,
                    [parameter(Mandatory = $true)]
                    [PSObject]
                    $App
                )
                $populatedApp = [PSCustomObject]@{
                    AppId             = $App.AppId
                    Name              = $App.Name
                    Version           = $App.Version
                    Publisher         = $App.Publisher
                    NoOfDependencies  = 0
                    NoOfDependentApps = 0
                    Dependencies      = @()
                    DependentApps     = @()
            
                }    
                foreach ($dependency in $App.Dependencies) {
                    $dependendApp = [PSCustomObject]@{
                        AppId             = $dependency.AppId
                        Name              = $dependency.Name
                        Version           = $dependency.Version
                        Publisher         = $dependency.Publisher
                        NoOfDependencies  = 0
                        NoOfDependentApps = 0
                        Dependencies      = @()
                        DependentApps     = @()
            
                    }
                    $populatedApp.Dependencies += $dependendApp
                }
                $populatedApp.NoOfDependencies = $populatedApp.Dependencies.Count
                $populatedApp
            
            }
            if ($IncludeUninstalled) {
                Write-Verbose "Getting all apps... "
            }
            else {
                Write-Verbose "Getting all installed apps... "
            }
            if ($IncludeUninstalled) {
                $allApps = Get-NavAppinfo -ServerInstance $ServerInstance -Tenant $Tenant -TenantSpecificProperties -Verbose:$false
            }
            else {
                $allApps = Get-NavAppinfo -ServerInstance $ServerInstance -Tenant $Tenant -TenantSpecificProperties -Verbose:$false | Where-Object { $_.IsInstalled -eq $true }
            }
            $allApps = Get-NavAppinfo -ServerInstance $ServerInstance -Tenant $Tenant -TenantSpecificProperties -Verbose:$false
            # There might be different versions of some apps (where the previous versions are uninstalled), remove these duplicates from the array
            $allAppsOnce = @()
            foreach ($app in $allApps) {
                $noOfInstances = ($allApps | Where-Object { $_.Name -eq $app.Name }).Count
                if ($noOfInstances -eq 1) {
                    $allAppsOnce += $app
                }
                else {
                    $isLatestVersion = $true
                    foreach ($specificApp in $allApps | Where-Object { $_.Name -eq $app.Name }) {
                        if (($specificApp -ne $app) -and ($app.Version -lt $specificApp.Version)) {
                            $isLatestVersion = $false
                        }
                    }
                    if ($isLatestVersion) {
                        $allAppsOnce += $app
                    }
                }    
            }
            $allApps = $allAppsOnce
            Write-Verbose "Found $($allApps.Count) apps"
            Write-Verbose "Populating dependencies for $($allApps.Count) apps"
            $appsPopulated = @()
            foreach ($app in $allApps) {
                $appInfo = Get-NavAppinfo -ServerInstance $ServerInstance -Id $app.AppId -Version $app.Version -Verbose:$false
                #if ($app.IsInstalled -eq $true) {
                $appsPopulated += Get-AppWithPopulatedDependencies -ServerInstance $ServerInstance -App $appInfo
                #}
            }
            $appsPopulated = $appsPopulated | Sort-Object -Property NoOfDependencies -Descending    
            Expand-DependentApps -Apps $appsPopulated
        }
        function Get-ReversedLookupOrder {
            [CmdletBinding()]
            param(
                [parameter(Mandatory = $true)]
                [PSObject]
                $Apps,
                [parameter(Mandatory = $true)]
                [string]
                $AppName,
                [parameter(Mandatory = $false)]
                [string]
                $AppPublisher
            )
            # Check recursively if the provided object has the given AppName as a dependent app
            function Test-DependentAppsContainApp {
                param(
                    [parameter(Mandatory = $true)]
                    [PSObject]
                    $App,
                    [parameter(Mandatory = $true)]
                    [string]
                    $AppName,
                    [parameter(Mandatory = $false)]
                    [string]
                    $AppPublisher
                )
                process {
                    $containsDependency = $false
                    
                    if ($AppPublisher) {
                        $containsDependency = $null -ne ($App.DependentApps | Where-Object { ($_.Publisher -eq $AppPublisher) -and ($_.Name -eq $AppName) })
                    }
                    else {
                        $containsDependency = $null -ne ($App.DependentApps | Where-Object { $_.Name -eq $AppName })
                    }
                    foreach ($dependency in $App.DependentApps) {
                        $containsDependency = $containsDependency -or (Test-DependentAppsContainApp -App $dependency -AppName $AppName -AppPublisher $AppPublisher)
                    }
                    $containsDependency
                }
            }
            # Removes all app-references from the (initially) complete object with all app references, to only provide the references to the desired AppName
            function Clear-AppObject {
                param(
                    [parameter(Mandatory = $true)]
                    $Apps,
                    [parameter(Mandatory = $true)]
                    [string]
                    $AppName,
                    [parameter(Mandatory = $false)]
                    [string]
                    $AppPublisher
                )
                process {
                    $elements = @()
                    foreach ($app in $Apps) {
                        if (Test-DependentAppsContainApp -App $app -AppName $AppName -AppPublisher $AppPublisher) {
                            $cleanedDependentApps = @()
                            foreach ($dependentApp in $app.DependentApps) {
                                if ($AppPublisher) {
                                    if (($dependentApp.Name -eq $AppName) -and ($dependentApp.Publisher -eq $AppPublisher)) {
                                        $dependentApp.DependentApps = $null
                                        $cleanedDependentApps += $dependentApp
                                    }
                                }
                                else {
                                    if ($dependentApp.Name -eq $AppName) {
                                        $dependentApp.DependentApps = $null
                                        $cleanedDependentApps += $dependentApp
                                    }
                                }
                                if (Test-DependentAppsContainApp -App $dependentApp -AppName $AppName -AppPublisher $AppPublisher) {
                                    $dependentApp = Clear-AppObject -Apps $dependentApp -AppName $AppName -AppPublisher $AppPublisher
                                    $cleanedDependentApps += $dependentApp
                                }
                            }
                            $app.DependentApps = $cleanedDependentApps
                            $elements += $app
                        }
                    }
                    $elements
                }
            }
            $Apps = Clear-AppObject -Apps $Apps -AppName $AppName -AppPublisher $AppPublisher
            $Apps
        }
        if (($AppName -eq "*") -and ($ReverseLookup)) {
            throw "You can not use Wildcard-search combined with 'ReverseLookup'"
            return
        }
        $AppInfo = Get-AppsWithDependencies -ServerInstance $ServerInstance -Tenant $Tenant -IncludeUninstalled:$IncludeUninstalled
        if ($ReverseLookup) {
            $AppInfo = Get-ReversedLookupOrder -Apps $AppInfo -AppName $AppName -AppPublisher $AppPublisher
            $AppInfo
        } 
        else {
            if ($AppName -eq "*") {
                $AppInfo
            }
            else {
                if ($AppPublisher) {
                    $AppInfo | Where-Object { ($_.Publisher -eq $AppPublisher) -and ($_.Name -eq $AppName) }    
                }
                else {
                    $AppInfo | Where-Object { $_.Name -eq $AppName }
                }
            }
        }
    }
}
Export-ModuleMember Get-AppDependencyInfo