Functions/AppManagement/Publish-BcSystem.ps1

<#
    .Synopsis
    Installs Microsoft System app and (optional) Microsoft System Application into the Microsoft Dynamics 365 Business Central database.
     
    .Description
    Installs Microsoft System app and (optional) Microsoft System Application into the Microsoft Dynamics 365 Business Central database.
     
    Proces flow:
        Loads Business Central Powershell modules corresponding to ServiceTier version
        Converts the Business Central database
        Starts the Business Central Server Instance
        (optional) Unpublishes previous Microsoft System Symbols
        Publishes the System Symbols for the new platform release
        Syncs the changes to SQL with Sync-NAVTenant
        (optional) Uninstalls previous Microsoft System Application
        (optional) Publish the new System Application
        Unpublish previous System Application(s)
        Install the new System Application
 
    .Example
    Publish-BcSystem -ServerInstance 'BC170' -SystemPath 'C:\System\Microsoft_System.app'
 
    .Example
    Publish-BcSystem -ServerInstance 'BC170' -SoftwarePath (Join-Path $PSScriptRoot 'System') -RemovePreviousVersions
 
    .Example
    Publish-BcSystem `
        -ServerInstance 'BC170' `
        -SystemPath 'C:\System\Microsoft_System.app' `
        -SystemApplicationPath 'C:\System\Microsoft_System Application.app' `
        -RemovePreviousVersions
#>


function Publish-BcSystem {
    [CmdletBinding(DefaultParameterSetName='SoftwarePath')]
    Param(
        # Publishes the symbols and extensions into the BC database attached to the supplied serverinstance.
        # E.g. 'BC170' or @('BC170Test','BC170Dev')
        [Parameter(Mandatory=$true)]    
        [string[]] $ServerInstance,
        
        # The path to the folder containing the system app and system application.
        # The folder should contain max one system app and one system application.
        # E.g. 'C:\system\'
        [Parameter(ParameterSetName='SoftwarePath', Mandatory = $true)]
        [string] $SoftwarePath,

        # FullName path to the Microsoft System you want to publish
        # E.g. 'C:\System\Microsoft_System.app'
        [Parameter(ParameterSetName='DirectPath', Mandatory = $true)]
        [string] $SystemPath,

        # FullName path to the Microsoft System Application you want to publish
        # E.g. 'C:\System\Microsoft_System_Application.app'
        [Parameter(ParameterSetName='DirectPath', Mandatory = $false)]
        [string] $SystemApplicationPath,

        # Location to write the logfile to. Default location is '?:\ProgramData\4ps\bcsystemdeployment'.
        # E.g. 'C:\BcInstallation\Log'
        [string] $LogFilePath = (Join-Path -Path $env:ProgramData -ChildPath '4ps\bcsystemdeployment'),

        # The customers Business Central software license file. E.g. 'C:\License\CustomerLicense.flf'
        # Important note for multi-tenant environments: When there are multiple tenants mounted on the serverinstance
        # and with the $Tenants parameter there are zero, two or more tenants specified; the license will be installed in
        # multiple tenants. If every tenant has it's own license, don't use this functionality. Upload it seperately.
        [string] $LicensePath,

        # When enabled it will removes all previous system symbols and system applications published in the database
        [switch] $RemovePreviousVersions
    )

    "
     _____ _ _ _ _ ____ _____ _
    | __ \ | | | (_) | | | _ \ / ____| | |
    | |__) | _| |__ | |_ ___| |__ | |_) | ___ | (___ _ _ ___| |_ ___ _ __ ___
    | ___/ | | | '_ \| | / __| '_ \ | _ < / __| \___ \| | | / __| __/ _ \ '_ `` _ \
    | | | |_| | |_) | | \__ \ | | | | |_) | (__ ____) | |_| \__ \ || __/ | | | | |
    |_| \__,_|_.__/|_|_|___/_| |_| |____/ \___| |_____/ \__, |___/\__\___|_| |_| |_|
                                                            __/ |
        4PS v{0} |___/
    "
 -f $MyInvocation.MyCommand.Module.Version | Write-Host

    # Validate PowerShell version
    if (-not $PSVersionTable.PSVersion.Major -ge '5') {

        $msg = 'Powershell 5.0 or higher required to continue. Current version is {0}.{1}' -f `
            $PSVersionTable.PSVersion.Major,
            $PSVersionTable.PSVersion.Minor
        throw $msg
    }
    'PowerShell version {0} is compatible.' -f $PSVersionTable.PSVersion.ToString() | Write-Host

    # Validate if PowerShell is started as administrator
    $windowsIdentity = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent())
    $elevated = ($windowsIdentity.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
    if (-not $elevated) {
        $msg  = 'You need administrative privileges to deploy Business Central system (application). '
        $msg += 'Start the script in a Powershell session launched as administrator.'
        throw $msg
    }
    'Script is executed as administrator.' | Write-Host

    # Validate if script is executed in a 64 bit process.
    if(-not [Environment]::Is64BitProcess){
        $msg = 'This script needs to be executed as a 64 bit process. Current process is x86 (32bit).'
        throw $msg
    }
    'Session is 64 bit.' | Write-Host
    
    try{ Stop-Transcript }catch{}

    # Create logfile
    $logFile = Join-Path -Path $LogFilePath -ChildPath ('{0}_{1}.log' -f 
                                (Get-Date).ToString('yyyy-MM-dd_HH.mm.ss'), $ServerInstance[0])
                                
    New-Item -Path $logFile -ItemType File -Force | Out-Null

    # Remove old logfiles (keep max 10 logfiles)
    $LogFiles = Get-ChildItem $LogFilePath -File -Filter ('*_{0}.log' -f $ServerInstance[0])
    if($LogFiles.Count -gt 10){
        $Exclude = $LogFiles | Sort-Object -Property CreationTime -Descending | Select-Object -First 10
        Get-ChildItem (Split-Path $logFile -Parent) -Exclude $Exclude | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    }

    Start-Transcript -path $logFile -append

    ### Script starts from here
    $SystemId    = '8874ed3a-0643-4247-9ced-7a7002f7135d'
    $SystemAppId = '63ca2fa4-4f03-4f2b-a480-172fef340d3f'

    $TaskStartTime = Write-StartProcessLine -StartLogText 'Importing Business Central PowerShell modules..'
        Import-BcModule `
            -ServerInstance $ServerInstance[0] `
            -ManagementModule `
            -AppManagementModule `
            -Force
    Write-EndProcessLine -TaskStartTime $TaskStartTime


    $TaskStartTime = Write-StartProcessLine -StartLogText 'Validating software path..'
        if([string]::IsNullOrEmpty($SoftwarePath) -eq $false){
            if((Test-Path $SoftwarePath)){
                'Software path exists: ''{0}''' -f $SoftwarePath | Write-Host

                $AppFiles = Get-ChildItem -Path $SoftwarePath -Filter '*.app'

                $AppFiles | ForEach-Object {
                    $AppGuid = (Get-NAVAppInfo -Path $_.FullName).AppId.Value.Guid
                    
                    if($AppGuid -eq $SystemId){
                        $SystemPath = $_.FullName

                        'System symbols found on path: {0}' -f $_.FullName | Write-Host
                    }
                    if($AppGuid -eq $SystemAppId){
                        $SystemApplicationPath = $_.FullName

                        'System Application found on path: {0}' -f $_.FullName | Write-Host
                    }
                }
            }
        }

        # Write app details to host
        Get-NAVAppInfo -Path $SystemPath | Select-Object -Property Name, AppId, Version | Format-List
        if([string]::IsNullOrEmpty($SystemApplicationPath) -eq $false){
            Get-NAVAppInfo -Path $SystemApplicationPath | Select-Object -Property Name, AppId, Version | Format-List
        }
    Write-EndProcessLine -TaskStartTime $TaskStartTime

    foreach ($ServerInstanceName in $ServerInstance){
        $TaskStartTime = Write-StartProcessLine -StartLogText ('Starting deployment to ServerInstance ''{0}''..' -f $ServerInstanceName)
        
        $ServerInstanceConfig = Get-BCServerInstance -ServerInstance $ServerInstanceName 

        $SubTaskStartTime = Write-StartProcessLine -StartLogText 'Convert the Business Central database..'
            "Invoke-NAVApplicationDatabaseConversion -DatabaseServer '{0}' -DatabaseName '{1}' -Force" -f 
                $ServerInstanceConfig.SqlInstance, $ServerInstanceConfig.DatabaseName | Write-Host
            Invoke-NAVApplicationDatabaseConversion `
                -DatabaseServer $ServerInstanceConfig.SqlInstance `
                -DatabaseName $ServerInstanceConfig.DatabaseName `
                -Force
            
            'Starting ServerInstance ''{0}''' -f $ServerInstanceName | Write-Host
            Set-NAVServerInstance `
                -ServerInstance $ServerInstanceName `
                -Start `
                -ErrorAction Stop

            Wait-BcServerInstanceMountingTenants `
                -ServerInstance $ServerInstanceName 

        Write-EndProcessLine -TaskStartTime $SubTaskStartTime

        $SubTaskStartTime = Write-StartProcessLine -StartLogText 'Deploy Microsoft System Symbols..' 
            if([string]::IsNullOrEmpty($LicensePath) -eq $false ){
                $TaskStartTime = Write-StartProcessLine -StartLogText 'Importing Business Central license file..'
                    Import-NAVServerLicense `
                                    -ServerInstance $ServerInstanceName `
                                    -LicenseFile $LicensePath `
                                    -Database 'NavDatabase' `
                                    -WarningAction SilentlyContinue
                Write-EndProcessLine -TaskStartTime $TaskStartTime
            }    
            if($RemovePreviousVersions){
                "Get-NAVAppInfo -ServerInstance '{0}' -SymbolsOnly" -f $ServerInstanceName | Write-Host
                Get-NAVAppInfo `
                    -ServerInstance $ServerInstanceName `
                    -SymbolsOnly | Unpublish-NAVApp
            }

            "Publish-NAVApp -ServerInstance '{0}' -Path '{1}' -PackageType SymbolsOnly" -f 
                $ServerInstanceName, $SystemPath | Write-Host
            Publish-NAVApp `
                -ServerInstance $ServerInstanceName `
                -Path $SystemPath `
                -PackageType SymbolsOnly `
                -SkipVerification
            
            "Sync-NAVTenant -ServerInstance '{0}' -Mode Sync -Force" -f $ServerInstanceName | Write-Host
            Sync-NAVTenant `
                -ServerInstance $ServerInstanceName `
                -Mode Sync `
                -Force
        Write-EndProcessLine -TaskStartTime $SubTaskStartTime
    
        if([string]::IsNullOrEmpty($SystemApplicationPath) -eq $false){
            $SubTaskStartTime = Write-StartProcessLine -StartLogText 'Deploy Microsoft System Application..' 
                'Uninstall previous Microsoft System Application' | Write-Host
                Get-NAVTenant -ServerInstance $ServerInstanceName | ForEach-Object {
                    Get-NAVAppInfo `
                        -ServerInstance $_.ServerInstance `
                        -Id $SystemAppId `
                        -Publisher 'Microsoft' | Uninstall-NAVApp -Tenant $_.Id -Force 
                }

                'Publish the new Microsoft System Application' | Write-Host
                "Publish-NAVApp -ServerInstance '{0}' -Path '{1}' -SkipVerification" -f 
                    $ServerInstanceName, $SystemApplicationPath | Write-Host
                Publish-NAVApp `
                    -ServerInstance $ServerInstanceName `
                    -Path $SystemApplicationPath `
                    -SkipVerification

                if($RemovePreviousVersions){
                    'Unpublish previous System Application(s)' | Write-Host
                    Get-NAVAppInfo `
                        -ServerInstance $ServerInstanceName `
                        -Id $SystemAppId `
                        -Publisher 'Microsoft' |
                        Where-Object -Property Version -ne $(Get-NAVAppInfo -Path $SystemApplicationPath).Version | 
                        Unpublish-NAVApp
                }

                'Install the new published System Application'
                Get-NAVTenant -ServerInstance $ServerInstanceName | ForEach-Object {
                    Get-NAVAppInfo `
                        -ServerInstance $_.ServerInstance `
                        -Id $SystemAppId | 
                        Sync-NAVApp -Mode ForceSync -Tenant $_.Id -Force
                    
                    Get-NAVAppInfo `
                        -ServerInstance $_.ServerInstance `
                        -Id $SystemAppId | 
                        Start-NAVAppDataUpgrade -Tenant $_.Id 
                }
            Write-EndProcessLine -TaskStartTime $SubTaskStartTime
        }
        Write-EndProcessLine -TaskStartTime $TaskStartTime
    }
}

Export-ModuleMember -Function Publish-BcSystem