Publish/Publish-BcAppsUsePowerShellNew.ps1
<#
.SYNOPSIS Publishes applications to an On-prem instance using PowerShell commands. .DESCRIPTION This function publishes applications to an On-prem instance of Business Central. It utilizes various parameters to control the publishing process, including server instance details, authentication context, and several flags to manage application synchronization and installation. .PARAMETER appPaths A collection of paths to the applications that will be published. .PARAMETER ServerInstance The name of the server instance where the applications will be published. .PARAMETER bcAuthContext The authentication context used to retrieve applications via the API. .PARAMETER force A flag that forces the execution of Sync-NAVApp. .PARAMETER uninstallApps A flag that removes all dependent applications. .PARAMETER ScopeTenant By default, applications are published as Global. If this parameter is used, applications will be published as PTE. .PARAMETER installUninstalledApps A flag that allows the installation of applications that were published but not installed. This works only for the versions you want to install. .PARAMETER portForServerInstance The port used to access the API to retrieve the installed applications. .Example Publish-BcAppsUsePowerShellNew ` -appPaths $appPaths ` -ServerInstance "localizationapps" ` -force:([bool]::Parse("${{ parameters.withForce }}")) ` -uninstallApps:([bool]::Parse("${{ parameters.uninstallApps }}")) ` -bcAuthContext $authContext ` -portForServerInstance 7148 #> function Publish-BcAppsUsePowerShellNew { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string[]]$appPaths, [Parameter(Mandatory = $true)] [string]$ServerInstance, [Hashtable]$bcAuthContext, [switch]$force, [switch]$uninstallApps, [switch]$ScopeTenant, [switch]$installUninstalledApps, [int]$portForServerInstance ) Write-Host "Force mode: $force" $appInfo = @{} $installApps = [ordered]@{} $removeApps = [ordered]@{} $targetApps = @() $DependentAppNames = @{} $DependentAppNamesWithoutTarget = @{} $UnpublishApps = @{} $maxAttempts = 5 $attempt = 0 $filteredAppPaths = @() $installedSmartApps = @{} $appsToUpdate = @{} $appNamesToUpdate = @() Get-BcManagementModule -ServerInstance $ServerInstance $installedExtensions = Get-NAVAppInfo -ServerInstance $ServerInstance foreach ($appPath in $appPaths) { $appJson = Get-AppJsonFromAppFile $appPath $appInfo[$appJson.name] = $appJson.version } foreach ($app in $appInfo.Keys) { $targetApps += $app } if ($portForServerInstance -ne $null -and $portForServerInstance) { $installedApps = Get-BcInstalledExtensionsOnPrem -serverInstance $ServerInstance -bcAuthContext $bcAuthContext -port $portForServerInstance } else { $installedApps = Get-BcInstalledExtensionsOnPrem -serverInstance $ServerInstance -bcAuthContext $bcAuthContext } #test Write-Host "##[group]Search apps not instaling" $matchingApps = @{} foreach ($app in $installedApps) { if ($app.publisher -eq "SMART business LLC" -and $app.isInstalled -eq $false) { $installedVersion = "$($app.versionMajor).$($app.versionMinor).$($app.versionBuild).$($app.versionRevision)" if ($appInfo.ContainsKey($app.displayName) -and $appInfo[$app.displayName] -eq $installedVersion) { $matchingApps[$app.displayName] = $installedVersion } } } $matchingApps if ($installUninstalledApps -and $matchingApps.Count -gt 0) { foreach ($app in $matchingApps.Keys) { $appName = $app $version = $matchingApps[$app] Write-Output "Installing $appName ...." Publish-BCApp -ServerInstance $ServerInstance -appName $appName -appVersion $version -Sync -force:$force -InstallDataUpgrade Write-Output "Successfully installed $appName version $version" } } else { if (-not $installUninstalledApps) { Write-Output "The parameter installUninstalledApps is set to false." } else { Write-Output "There are no apps to install." } } Write-Host "##[endgroup]" #test foreach ($app in $installedApps) { if ($app.publisher -eq "SMART business LLC" -and $app.isInstalled -eq $true) { $version = "$($app.versionMajor).$($app.versionMinor).$($app.versionBuild).$($app.versionRevision)" $installedSmartApps[$app.displayName] = $version } } Write-Host "##[group]Apps installed on the environment" $installedSmartApps Write-Host "##[endgroup]" foreach ($appName in $appInfo.Keys) { if ($installedSmartApps.ContainsKey($appName)) { $installedVersion = [version]$installedSmartApps[$appName] $newVersion = [version]$appInfo[$appName] if ($newVersion -gt $installedVersion) { $appsToUpdate[$appName] = $appInfo[$appName] } } else { $appsToUpdate[$appName] = $appInfo[$appName] } } Write-Host "##[group]Sorted apps to install" echo "List of applications to be installed" $appsToUpdate Write-Host "##[endgroup]" foreach ($appName in $appsToUpdate.Keys) { $appNamesToUpdate += $appName } foreach ($appPath in $appPaths) { $appJson = Get-AppJsonFromAppFile $appPath $appName = $appJson.name if ($appNamesToUpdate -contains $appName) { $filteredAppPaths += $appPath } } $sortApps = Sort-AppFilesByDependencies -appFiles $filteredAppPaths 3> $null foreach ($appPath in $sortApps) { $appJson = Get-AppJsonFromAppFile $appPath $appName = $appJson.name $appVersion = $appJson.version $installApps[$appName] = @{ Path = $appPath; Version = $appVersion } } $DependentAppNames = Get-DependentApps -ServerInstance $ServerInstance -TargetApps $appNamesToUpdate -IncludeTargetApps $DependentAppNamesWithoutTarget = Get-DependentApps -ServerInstance $ServerInstance -TargetApps $appNamesToUpdate Write-Host "##[group]Uninstall dependencies of dependent apps (Optionally)" 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" } } } } } else { Write-Output "The uninstallApps parameter was not set to True." } Write-Host "##[endgroup]" Write-Host "##[group]Install new app" 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 -Publish -Sync -InstallDataUpgrade -ScopeTenant } else { Publish-BCApp -ServerInstance $ServerInstance -appPath $Path -appName $appName -appVersion $version -force:$force -Publish -Sync -InstallDataUpgrade } } } } else { Write-Host "No apps found for installation" } Write-Host "##[endgroup]" Write-Host "##[group]Unpublish the old version of the applications that we have updated" foreach ($appName in $appNamesToUpdate) { $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 { try { $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 } } catch { Write-Output "Exception occurred while unpublishing $appName version $version. Error: $_" 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 } } Write-Host "##[endgroup]" $remainingDependentApps = $DependentAppNamesWithoutTarget | Where-Object { $installApps.keys -notcontains $_.Name } Write-Host "##[group]Installing all dependencies" echo "Installing all dependencies that were uninstall to install new applications" $remainingDependentApps if ($remainingDependentApps.Count -gt 0) { foreach ($app in $remainingDependentApps.Keys) { $appName = $app $version = $remainingDependentApps[$app] Write-Output "Installing $appName ...." Publish-BCApp -ServerInstance $ServerInstance -appName $appName -appVersion $version -InstallDataUpgrade Write-Output "Successfully Install $appName version $version" } } Write-Host "##[endgroup]" } <# .SYNOPSIS Publishes a Business Central application to an On-prem instance using PowerShell commands. .DESCRIPTION This function publishes a Business Central application to an On-prem instance. It includes parameters to control the publishing, synchronization, and installation processes, allowing for flexible and precise management of the application lifecycle. .PARAMETER ServerInstance The name of the server instance where the application will be published. .PARAMETER appPath The path to the application file that will be published. .PARAMETER appName The name of the application to be published. .PARAMETER appVersion The version of the application to be published. .PARAMETER force A flag that forces the execution of Sync-NAVApp. .PARAMETER Publish A flag that triggers the publishing of the application. .PARAMETER Sync A flag that triggers the synchronization of the application. .PARAMETER InstallDataUpgrade A flag that allows the installation or upgrade of the application data. .PARAMETER ScopeTenant If this parameter is used, the application will be published as Tenant-specific (PTE). Otherwise, it will be published as Global. #> 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]$Publish, [switch]$Sync, [switch]$InstallDataUpgrade, [switch]$ScopeTenant ) if ($Publish) { #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 #} } if ($Sync) { #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-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 } |