Publish/Publish-BcAppsUsePowerShellNew.ps1

function Publish-BcAppsUsePowerShellNew {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$appPaths,
        [Parameter(Mandatory = $true)]
        [string]$ServerInstance,
        [switch]$force,
        [switch]$uninstallApps,
        [switch]$ScopeTenant
    )

    Write-Host "Force mode: $force"

    $appInfo = @{}
    $installApps = [ordered]@{}
    $removeApps = [ordered]@{}
    $targetApps = @()
    $DependentAppNames = @{}
    $DependentAppNamesWithoutTarget = @{}
    $UnpublishApps = @{}
    $maxAttempts = 5
    $attempt = 0

    Get-BcManagementModule -ServerInstance $ServerInstance

    $installedExtensions = Get-NAVAppInfo -ServerInstance $ServerInstance
    $sortApps = Sort-AppFilesByDependencies -appFiles $appPaths 3> $null

    foreach ($appPath in $appPaths) {
        $appJson = Get-AppJsonFromAppFile $appPath
        $appInfo[$appJson.name] = $appJson.version
    }
    
    foreach ($app in $appInfo.Keys) {
        $targetApps += $app
    }

    foreach ($appPath in $sortApps) {
        $appJson = Get-AppJsonFromAppFile $appPath
        $appName = $appJson.name
        $appVersion = $appJson.version

        $installApps[$appName] = @{ Path = $appPath; Version = $appVersion }
    }

    echo "Found dependencies apps"
    $DependentAppNames = Get-DependentApps -ServerInstance $ServerInstance -TargetApps $targetApps -IncludeTargetApps
    $DependentAppNamesWithoutTarget = Get-DependentApps -ServerInstance $ServerInstance -TargetApps $targetApps
    $DependentAppNames

    echo "Uninstall dependencies of dependent apps"
    if ($uninstallApps) {
        foreach ($entry in $DependentAppNames) {
            $appName = $entry.Key
            $versions = $entry.Value
            if ([string]::IsNullOrEmpty($appName) -or [string]::IsNullOrEmpty($versions)) {
                Write-Output "The app is not published on the environment: $appName"
            } else {
                foreach ($version in $versions) {
                    if ([string]::IsNullOrEmpty($version)) {
                        Write-Output "The app version is not specified for $appName"
                    } else {
                        Uninstall-NAVApp -ServerInstance $ServerInstance -Name $appName -Version $version
                        Write-Output "Successfully uninstalled $appName version $version"
                    }
                }
            }
        }
    }

    echo "Install new apps"
    if ($installApps.Count -gt 0) {
        foreach ($appName in $installApps.Keys) {
            $appData = $installApps[$appName]
            $versionList = $appData["Version"]
            $Path = $appData["Path"]
            echo "versionList $versionList"
            echo "Path $Path"
            foreach ($version in $versionList) {
                if ($ScopeTenant) {
                    Publish-BCApp -ServerInstance $ServerInstance -appPath $Path -appName $appName -appVersion $version -force:$force -PublishSync -InstallDataUpgrade -ScopeTenant
                } else {
                    Publish-BCApp -ServerInstance $ServerInstance -appPath $Path -appName $appName -appVersion $version -force:$force -PublishSync -InstallDataUpgrade
                }
            }
        }
    }

    echo "Unpublish the old version of the applications that we have updated"
    foreach ($appName in $targetApps) {
        $matchingApp = $DependentAppNames | Where-Object { $_.Name -eq $appName }
        if ($matchingApp) {
            $UnpublishApps[$appName] = $matchingApp.Value
        }
    }

    $remainingApps = $UnpublishApps.Clone()
    echo "Unpublish apps:"
    $remainingApps

    while ($attempt -lt $maxAttempts -and $remainingApps.Count -gt 0) {
        $attempt++
        Write-Output "Attempt $attempt of $maxAttempts"
        $failedApps = @{}

        foreach ($entry in $remainingApps.GetEnumerator()) {
            $appName = $entry.Key
            $versions = $entry.Value
            if ([string]::IsNullOrEmpty($appName) -or [string]::IsNullOrEmpty($versions)) {
                Write-Output "The app is not published on the environment: $appName"
            } else {
                foreach ($version in $versions) {
                    if ([string]::IsNullOrEmpty($version)) {
                        Write-Output "The app version is not specified for $appName"
                    } else {
                        if ($installApps.Contains($appName) -and $installApps[$appName].Version -eq $version) {
                            Write-Output "Skipping unpublish for $appName version $version as it was just installed."
                        } else {
                            $UnpublishResult = $null
                            & { Unpublish-NAVApp -ServerInstance $ServerInstance -Name $appName -Version $version } *>&1 | Tee-Object -Variable UnpublishResult
                            if ([string]::IsNullOrEmpty($UnpublishResult)) {
                                Write-Output "Successfully Unpublish $appName version $version"
                            } else {
                                Write-Output "Failed to Unpublish $appName version $version. Error: $UnpublishResult"
                                if (-not $failedApps.ContainsKey($appName)) {
                                    $failedApps[$appName] = @()
                                }
                                $failedApps[$appName] += $version
                            }
                        }
                    }
                }
            }
        }

        if ($failedApps.Count -gt 0) {
            Write-Output "Retrying failed apps..."
            $remainingApps = $failedApps.Clone()
        } else {
            Write-Output "All apps successfully unpublished."
            break
        }
    }

    $remainingDependentApps = $DependentAppNamesWithoutTarget | Where-Object { $installApps.keys -notcontains $_.Name }
    
    echo "Installing all dependencies"
    $remainingDependentApps

  if ($remainingDependentApps.Count -gt 0) {
        foreach ($app in $remainingDependentApps.GetEnumerator()) {
            $appName = "$($app.Key)"
            $version = "$($app.Value)"
            Write-Output "Installing $appName ...."
            Publish-BCApp -ServerInstance $ServerInstance -appName $appName -appVersion $version -InstallDataUpgrade
            Write-Output "Successfully Install $appName version $version"
    }
}

}

function Publish-BCApp {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ServerInstance,
        [string]$appPath,
        [Parameter(Mandatory = $true)]
        [string]$appName,
        [Parameter(Mandatory = $true)]
        [string]$appVersion,
        [switch]$force,
        [switch]$PublishSync,
        [switch]$InstallDataUpgrade,
        [switch]$ScopeTenant
    )

    if ($PublishSync) {
        #try {
        Write-Host "Publishing $appName ... "
        if ($ScopeTenant) {
            Publish-NAVApp -ServerInstance $ServerInstance -Path $appPath -SkipVerification -Force -Scope Tenant
        } else {
            Publish-NAVApp -ServerInstance $ServerInstance -Path $appPath -SkipVerification -Force
        }
        #}
        #catch {
        # Write-Error "Failed to publish $appName version $appVersion to $ServerInstance : $_"
        # return
        #}

        #try {
        Write-Host "Syncing $appName ... "
        if ($force) {
            Sync-NAVApp -ServerInstance $ServerInstance -Name $appName -Version $appVersion -Mode ForceSync -ErrorAction Stop
        } else {
            Sync-NAVApp -ServerInstance $ServerInstance -Name $appName -Version $appVersion -ErrorAction Stop
        }
        #}
        #catch {
        # Write-Error "Failed to synchronize $appName version $appVersion on $ServerInstance : $_"
        # return
        #}
    }

    $navAppInfoFromDb = Get-NAVAppInfo -ServerInstance $ServerInstance -Name $appName -Version $appVersion -Tenant "default" -TenantSpecificProperties
    if ($null -eq $navAppInfoFromDb.ExtensionDataVersion -or $navAppInfoFromDb.ExtensionDataVersion -eq  $navAppInfoFromDb.Version) {
        $install = $true
    } else {
        $upgrade = $true
    }

    if ($InstallDataUpgrade) {
        if ($install) {
            #try {
            Write-Host "Installing $appName ... "
            Install-NAVApp -ServerInstance $ServerInstance -Name $appName -Version $appVersion 
            #}
            #catch {
            # Write-Error "Failed to install $appName version $appVersion on $ServerInstance : $_"
            # return
            #}
        }

        if ($upgrade) {
            #try {
            Write-Host "Upgrading $appName ... "
            Start-NAVAppDataUpgrade -ServerInstance $ServerInstance -Name $appName -Version $appVersion 
            #}
            #catch {
            # Write-Error "Failed to start data upgrade for $appName version $appVersion on $ServerInstance : $_"
            # return
            #}
        }
    }

    Write-Output "Successfully published $appName version $appVersion to $ServerInstance`n"
}

function Get-BcManagementModule {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$ServerInstance
    )

    $registryPath = Join-Path -Path "HKLM:\SYSTEM\ControlSet001\Services" -ChildPath "MicrosoftDynamicsNavServer`$$ServerInstance"

    try {
        if (-not (Test-Path -Path $registryPath)) {
            throw "Registry path not found: $registryPath"
        }

        $imagePath = (Get-ItemProperty -Path $registryPath -Name ImagePath).ImagePath
        if ($imagePath -notmatch '^(.*?)\\Microsoft\.Dynamics\.Nav\.Server\.exe') {
            throw "Executable path not found in ImagePath. Extracted string: $imagePath"
        }

        $serverPath = $matches[1] -replace '"', ''
        $managementModulePath = Join-Path -Path $serverPath -ChildPath "Microsoft.Dynamics.Nav.Management.psm1"

        if (-not (Test-Path -Path $managementModulePath)) {
            throw "Management module not found at path: $managementModulePath"
        }

        Import-Module "$managementModulePath" -ErrorAction Stop
        Write-Host "Import-Module successful"
    }
    catch {
        Write-Error "An error occurred: $_"
    }
}

function Download-And-ExtractArchive {
    param (
        [Parameter(Mandatory = $true)]
        [string]$zipUrl,       
        [Parameter(Mandatory = $true)]
        [string]$destination   
    )
    try {
        $tempDir = "$Env:BUILD_ARTIFACTSTAGINGDIRECTORY"
        $zipPath = Join-Path -Path $tempDir -ChildPath "apps.zip"
        
        Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath
        Expand-Archive -Path $zipPath -DestinationPath $destination -Force
        Write-Output "Archive downloaded and extracted to $destination"
        
    } catch {
        Write-Error "An error occurred: $_"
    }
}

function Get-DependentApps {
    param (
        [string]$ServerInstance,
        [string[]]$TargetApps,
        [switch]$IncludeTargetApps
    )

    $apps = Get-NAVAppInfo -ServerInstance $ServerInstance -Publisher "SMART business LLC"

    $dependenciesDict = @{}

    foreach ($app in $apps) {
        $appDependencies = (Get-NAVAppInfo -ServerInstance $ServerInstance -Name $app.Name).Dependencies

        $filteredDependencies = $appDependencies | Where-Object { $_.Publisher -ne "Microsoft" }

        $dependenciesDict[$app.Name] = $filteredDependencies | ForEach-Object {
            @{
                Name = $_.Name
                Version = $_.Version
            }
        }
    }

    $dependentApps = @{}

    $dependenciesDict.GetEnumerator() | ForEach-Object {
        $appName = $_.Key
        $dependencies = $_.Value

        $isDependent = $dependencies | Where-Object { $targetApps -contains $_.Name }

        if ($isDependent) {
            $dependentApps[$appName] = ($apps | Where-Object { $_.Name -eq $appName }).Version
        }
    }

    if ($IncludeTargetApps) {
        $targetApps | ForEach-Object {
            $dependentApps[$_] = ($apps | Where-Object { $_.Name -eq $_ }).Version
        }
    }

    $keysToUpdate = $dependentApps.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }

    foreach ($key in $keysToUpdate) {
        $dependentApps[$key] = ($apps | Where-Object { $_.Name -eq $key }).Version
    }

    return $dependentApps.GetEnumerator() | Sort-Object -Property Name -Unique
}