Publishing extensions to Business Central OnPremises

Extension installation methods

When an extension is developed, it can be installed to an on-premises environment basically by using two methods:

1) Installation with Visual Studio Code: This method is natural during the development when you have to test the functionality every now and then. The installation is executed simply by pressing F5 or Ctr-F5, depending if you need to debug the code or not. Smartest RAD developers throw Alt to these combinations for quick publish. This approach is not recommended to use with production environments, since it requires Visual Studio Code and add-ons and a bunch of other stuff installed on the server.

2) installation with PowerShell: This method is recommended to use with production servers. You do not need to copy source code to the installation location, only .app file is required.

3) Use Waldo's workaround and install straight from DevOps https://www.waldo.be/2020/06/15/deploying-from-devops-the-right-way-enabling-external-deployment-in-onprem-business-central-environments/ This is the approach I recommend to everyone, if possible. In our cases it unfortunately just is not possible for various sad reasons I will not start listing here ;)

There are some pitfalls developer can fall in both of the first approaches. VSC approach requires exactly correct version of AL extension which has to be installed from .vsix file that is acquired from the installation CD, and specifically crafted launch.json file that points to the correct installation point. PowerShell approach can be very complex when you have to pass different paths to .app file and preload correct .dll files before installation, not to mention the connection endpoint where you publish the extension.

Sample launch.json file

Microsoft has written excellent instructions how to install an extension in MS Docs. Please refer this link for reference: https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-how-publish-and-install-an-extension-v2

As any casual reader can easily see, there is a loads of parameters that affect whether poor developers has success or not. Usually the installation script looks something like this:

#Load dll
Import-Module 'C:\Program Files\Microsoft Dynamics 365 Business Central\140\Service\Microsoft.Dynamics.Nav.Management.dll' -ErrorVariable errorVariable -WarningAction SilentlyContinue

#Uninstall dependencies
Uninstall-NAVApp -ServerInstance BC140 -Name 'SQ Customer Finland Business Logic' -Version '1.0.35916.2'
Unpublish-NAVApp -ServerInstance BC140 -Name 'SQ Customer Finland Business Logic' -Version '1.0.35916.2'

Uninstall-NAVApp -ServerInstance BC140 -Name 'SQ Customer WebShop Extension' -Version '1.0.35916.19'
Unpublish-NAVApp -ServerInstance BC140 -Name 'SQ Customer WebShop Extension' -Version '1.0.35916.19'

#Uninstall solution
Uninstall-NAVApp -ServerInstance BC140 -Name 'SQ Customer Finland Base' -Version '1.0.35916.3'
Unpublish-NAVApp -ServerInstance BC140 -Name 'SQ Customer Finland Base' -Version '1.0.35916.3'

#source app filename: 'C:\Deliveries\Extensions\Solteq\Solteq_SQ Customer Finland Base_1.0.35916.4.app'
#Publish and install new version
Publish-NAVApp -ServerInstance BC140 -Path 'C:\Deliveries\Extensions\Solteq\Solteq_SQ Customer Finland Base_1.0.35916.4.app' -SkipVerification -Force
Sync-NAVTenant -ServerInstance BC140 -Force
Sync-NAVApp -ServerInstance BC140 -Name 'SQ Customer Finland Base' -Version '1.0.35916.4' -Force
Install-NAVApp -ServerInstance BC140 -Name 'SQ Customer Finland Base' -Version '1.0.35916.4' -Force

#Reinstall dependencies
Publish-NAVApp -ServerInstance BC140 -Path 'C:\Deliveries\Extensions\Solteq\Solteq_SQ Customer Finland Business Logic_1.0.35916.2.app' -SkipVerification -Force
Install-NAVApp -ServerInstance BC140 -Name 'SQ Customer Finland Business Logic' -Version '1.0.35916.2' -Force

Publish-NAVApp -ServerInstance BC140 -Path 'C:\Deliveries\Extensions\Solteq\Solteq_SQ Customer WebShop Extension_1.0.35916.3.app' -SkipVerification -Force
Install-NAVApp -ServerInstance BC140 -Name 'SQ Customer WebShop Extension' -Version '1.0.35916.3' -Force

The code above may or may not be perfect with correct paths to each .app file and correct versions you have to type multiple times, but this is what you basically get when you do your first tries with PowerShell. Complex, prone to errors and with a lot of trial and error it may turn into something more elaborate.

Extended PowerShell Script

I have been elaborating the script above, and now I think I have the script in publishable condition.

$ServicePath = 'C:\Program Files\Microsoft Dynamics 365 Business Central\140\Service\'  #Business Central Spring 2019
$ServiceName = 'BC140'
$Publisher = 'Solteq'

$ErrorActionPreference = "Stop"
$BaseApppath = "$($PSScriptRoot)\"

#Requires -RunAsAdministrator
Import-Module ($ServicePath + 'Microsoft.Dynamics.Nav.Management.dll') -ErrorVariable errorVariable -WarningAction SilentlyContinue | Out-Null
Import-Module ($ServicePath + 'Microsoft.Dynamics.Nav.Model.Tools.dll') -ErrorVariable errorVariable -WarningAction SilentlyContinue | Out-Null
Import-Module ($ServicePath + 'Microsoft.Dynamics.Nav.Apps.Management.dll') -ErrorVariable errorVariable -WarningAction SilentlyContinue | Out-Null

function Remove ($_AppVersion)
{
    if ($_AppVersion) {
        $ExtensionsToRemove = Get-NAVAppInfo -ServerInstance $ServiceName | where {($_.Name -eq "$AppName") -and ($_.Version -eq $_AppVersion)}
    } else {
        $ExtensionsToRemove = Get-NAVAppInfo -ServerInstance $ServiceName | where {($_.Name -eq "$AppName")}
    }
    if ($ExtensionsToRemove) {
        foreach ($ExtensionToRemove in $ExtensionsToRemove) {
            Write-Host Uninstalling app "$AppName" version $ExtensionToRemove.version ...
            Uninstall-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $ExtensionToRemove.version
            Unpublish-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $ExtensionToRemove.version
        }
    } else {Write-Host $AppName not found, skipping...}
}

function RemoveDependencies ($_Dependencies)
{
    $tmpAppName = $AppName
    Write-Host Removing dependencies for $AppName : $_Dependencies
    foreach ($Dependency in $_Dependencies){
        $AppName = $Dependency
        Remove
    }
    $AppName = $tmpAppName
}

function Install
{
    Clear-Variable My*
    $NewAppPath = $BaseApppath + $Publisher + '_' + $AppName + '_' + $NewVersion + '.app'
    $MyOldExtensions = Get-NAVAppInfo -ServerInstance $ServiceName | where {($_.Name -eq "$AppName")}
    if ($MyOldExtensions) {
        $m = $MyOldExtensions | measure
        write-host Found $m.Count old versions: $MyOldExtensions.version
        foreach ($MyOldExtension in $MyOldExtensions) {
            Remove $MyOldExtension.version
        }
    }
    Write-Host Publishing $AppName version $NewVersion
    Publish-NAVApp -ServerInstance $ServiceName -Path $NewAppPath -SkipVerification -Force
    Sync-NAVTenant -ServerInstance $ServiceName -Force
    Sync-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force
#    Sync-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force -Mode ForceSync
    if ($MyOldExtension -and $MyOldExtension.version -ne $NewVersion) {
        Write-Host Upgrading $AppName version $MyOldExtension.version to version $NewVersion
        Start-NAVAppDataUpgrade -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force
    }
    Install-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force
}

#Customer Finland Base
$AppName = 'SQ Customer Finland Base'
$NewVersion = '1.0.35916.4'
RemoveDependencies 'SQ Customer Finland Business Logic','SQ Customer WebShop Extension'
Install

#RefenceNo
$AppName = 'SQ Reference Number for Invoices'
$NewVersion = '1.0.0.1'
RemoveDependencies 'SQ Document layout templates', 'SQ Finvoice'
Install

#Shipment Extension
$AppName = 'SQ Shipment Extension'
$NewVersion = '1.0.35916.11'
Install

#Sepa
$AppName = 'SQ SEPA payments to bank'
$NewVersion = '1.0.0.2'
Install

#GL Interface
$AppName = 'SQ GL Interface'
$NewVersion = '1.2.0.5'
$Publisher = 'SQ'
Install
$Publisher = 'Solteq'

#Customer Finland Business Logic
$AppName = 'SQ Customer Finland Business Logic'
$NewVersion = '1.0.35916.42'
Install
#Remove

#Document Layout Templates
$AppName = 'SQ Document layout templates'
$NewVersion = '1.0.35916.19'
Install
#Remove

#Finvoice
$AppName = 'SQ Finvoice'
$NewVersion = '1.0.35916.16'
Install

#Customer Finland WebShop Extension
$AppName = 'SQ Customer WebShop Extension'
$NewVersion = '1.0.35916.6'
Install

Parametrization

First of all, you have to save the script to a preferrably standardized path where you can find it from every customer you are using it. For example "C:\Deliverables\Extensions". Then you download .app files from DevOps or whatever you use for your source code management system (you use something, don't you?).

Next you have to change some parameters in the script. Open the script with PowerShell ISE as an admin. The Powershell commands that you need to use require admin rights.

The code below contains some parameters you have to change, but they are self-explanatory if you have already done some manual installations. Each of the extensions have a section of their own to set AppName and NewVersion variables, as well as remove possible dependant modules before installing new version of the extension.

Basically you have to insert the name of the extension, and the desired version you want to install. The script knows how to build the app filename from these variables, and knows how to install the extension.

The code has samples also how to handle extensions from multiple different publishers. The script can also be used if you only have to uninstall/unpublish an extension. The code below is also attached as text to this blog post for your convenience. Maybe some day I have a GitHub repository of my own for these scripts, but meanwhile this blog will do.

$ServicePath = 'C:\Program Files\Microsoft Dynamics 365 Business Central\140\Service\'  #Business Central Spring 2019
$ServiceName = 'BC140'
$Publisher = 'Solteq'

$ErrorActionPreference = "Stop"
$BaseApppath = "$($PSScriptRoot)\"

#Requires -RunAsAdministrator
Import-Module ($ServicePath + 'Microsoft.Dynamics.Nav.Management.dll') -ErrorVariable errorVariable -WarningAction SilentlyContinue | Out-Null
Import-Module ($ServicePath + 'Microsoft.Dynamics.Nav.Model.Tools.dll') -ErrorVariable errorVariable -WarningAction SilentlyContinue | Out-Null
Import-Module ($ServicePath + 'Microsoft.Dynamics.Nav.Apps.Management.dll') -ErrorVariable errorVariable -WarningAction SilentlyContinue | Out-Null

function Remove ($_AppVersion)
{
    if ($_AppVersion) {
        $ExtensionsToRemove = Get-NAVAppInfo -ServerInstance $ServiceName | where {($_.Name -eq "$AppName") -and ($_.Version -eq $_AppVersion)}
    } else {
        $ExtensionsToRemove = Get-NAVAppInfo -ServerInstance $ServiceName | where {($_.Name -eq "$AppName")}
    }
    if ($ExtensionsToRemove) {
        foreach ($ExtensionToRemove in $ExtensionsToRemove) {
            Write-Host Uninstalling app "$AppName" version $ExtensionToRemove.version ...
            Uninstall-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $ExtensionToRemove.version
            Unpublish-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $ExtensionToRemove.version
        }
    } else {Write-Host $AppName not found, skipping...}
}

function RemoveDependencies ($_Dependencies)
{
    $tmpAppName = $AppName
    Write-Host Removing dependencies for $AppName : $_Dependencies
    foreach ($Dependency in $_Dependencies){
        $AppName = $Dependency
        Remove
    }
    $AppName = $tmpAppName
}

function Install
{
    Clear-Variable My*
    $NewAppPath = $BaseApppath + $Publisher + '_' + $AppName + '_' + $NewVersion + '.app'
    $MyOldExtensions = Get-NAVAppInfo -ServerInstance $ServiceName | where {($_.Name -eq "$AppName")}
    if ($MyOldExtensions) {
        $m = $MyOldExtensions | measure
        write-host Found $m.Count old versions: $MyOldExtensions.version
        foreach ($MyOldExtension in $MyOldExtensions) {
            Remove $MyOldExtension.version
        }
    }
    Write-Host Publishing $AppName version $NewVersion
    Publish-NAVApp -ServerInstance $ServiceName -Path $NewAppPath -SkipVerification -Force
    Sync-NAVTenant -ServerInstance $ServiceName -Force
    Sync-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force
#    Sync-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force -Mode ForceSync
    if ($MyOldExtension -and $MyOldExtension.version -ne $NewVersion) {
        Write-Host Upgrading $AppName version $MyOldExtension.version to version $NewVersion
        Start-NAVAppDataUpgrade -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force
    }
    Install-NAVApp -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force
}

#Customer Finland Base
$AppName = 'SQ Customer Finland Base'
$NewVersion = '1.0.35916.4'
RemoveDependencies 'SQ Customer Finland Business Logic','SQ Customer WebShop Extension'
Install

#RefenceNo
$AppName = 'SQ Reference Number for Invoices'
$NewVersion = '1.0.0.1'
RemoveDependencies 'SQ Document layout templates', 'SQ Finvoice'
Install

#Shipment Extension
$AppName = 'SQ Shipment Extension'
$NewVersion = '1.0.35916.11'
Install

#Sepa
$AppName = 'SQ SEPA payments to bank'
$NewVersion = '1.0.0.2'
Install

#GL Interface
$AppName = 'SQ GL Interface'
$NewVersion = '1.2.0.5'
$Publisher = 'SQ'
Install
$Publisher = 'Solteq'

#Customer Finland Business Logic
$AppName = 'SQ Customer Finland Business Logic'
$NewVersion = '1.0.35916.42'
Install
#Remove

#Document Layout Templates
$AppName = 'SQ Document layout templates'
$NewVersion = '1.0.35916.19'
Install
#Remove

#Finvoice
$AppName = 'SQ Finvoice'
$NewVersion = '1.0.35916.16'
Install

#Customer Finland WebShop Extension
$AppName = 'SQ Customer WebShop Extension'
$NewVersion = '1.0.35916.6'
Install

Known issues

There is two known issues I may or may not fix later. So far this version works nicely for me, so there has not been need for further development.

1) On rare occasions the old version does not want to get uninstalled, and I have to run manually Start-NAVAppDataUpgrade command like below. It is easy to select and use PowerShell Run Selection tool to upgrade the solution.

Start-NAVAppDataUpgrade -ServerInstance $ServiceName -Name $AppName -Version $NewVersion -Force

2) Sometimes when you have done a breaking change, for example changed database schema by changing a table field to something else or something else very nasty. The script does not handle breaking changes, and you should be ashamed and do it manually just to teach you a lesson not to do such things! ;)

//UrpoK

Comment List
Anonymous
Related
Recommended