Containers/New-Container.ps1

<#
 .Synopsis
  Creates a new NAV/BC container
 .Description
  Creates a new NAV/BC container
 .Parameter ContainerName
  Name of the container
 .Parameter Version
  Version of NAV or BC to create
 .Parameter LicenseFile
  Path to license file. It can be an Uri and then the license will be downloaded
 .Parameter Credential
  Credentials to be used for login to the container.
 .Parameter Country
  Country version of the container.
 .Parameter ArtifactUrl
  Specifies the Artifact Url
 .Parameter databaseBackup
  Defines the BAK or bacpac of a database to use to restore into the container
 .Parameter sqlServer
  Specify an external SQL Server, if the database is already there or should be deployed on the SQL Server
 .Parameter sqlDatabase
  Specify a database on an external SQL Server
 .Parameter sqlCredential
  Specify credentials to connect to the external SQL Server
 .Parameter memoryLimit
  Defines the memory limit for the docker container. The default limit is 12GB
 .Parameter singleTenant
  Defines, if the database is single tenant
 .Parameter skipAlwaysPull
  Always pull latest version of the docker image be default. This overrides the default
 .Parameter SetupTestUsers
  Creates test users in the container after it is created
 .Parameter AddTestTool
  Add this switch if you do not want to install the test tool
  .Parameter skipBackup
  Add this switch if you do not want to use database backups from previous containers
  .Parameter useHyperVIsolation
  Add this switch if you want to force Hyper V isolation when creating the container
  .Parameter enableTaskScheduler
  Include this switch if you want to do Enable the Task Scheduler
  .Parameter disableHttps
  Turns off SSL for access to the environment
  .Example
  New-Container -SetupTestUsers
#>

function New-Container {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact="low")]
    Param(
        [Parameter(Mandatory = $false)]
        [string] $ContainerName,
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Version')]
        [string] $Version,
        [Parameter(Mandatory = $false,
            ParameterSetName = 'Version')]
        [string] $CU = "",
        [Parameter(Mandatory = $false,
            ParameterSetName = 'Version')]
        [string] $Country = "",
        [Parameter(Mandatory = $false)]
        [string] $LicenseFile,
        [Parameter(Mandatory = $false)]
        [pscredential] $Credential,
        [Parameter(Mandatory = $false)]
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Artifact')]
        [Parameter(Mandatory = $false,
            ParameterSetName = 'SQLServer')]
        [string] $ArtifactUrl = "",
        [Parameter(Mandatory = $false)]
        [ValidateSet('', 'OnPrem','Sandbox')]
        [string] $target = "",
        [Parameter(Mandatory = $false)]
        [string] $databaseBackup = "",
        [Parameter(Mandatory = $false)]
        [Parameter(Mandatory = $true,
            ParameterSetName = 'SQLServer')]
        [string] $sqlServer = "",
        [Parameter(Mandatory = $false)]
        [Parameter(Mandatory = $false,
            ParameterSetName = 'SQLServer')]
        [string] $sqlInstance = "",
        [Parameter(Mandatory = $false)]
        [Parameter(Mandatory = $false,
            ParameterSetName = 'SQLServer')]
        [string] $sqlDatabase = "",
        [Parameter(Mandatory = $false)]
        [Parameter(Mandatory = $true,
            ParameterSetName = 'SQLServer')]
        [pscredential] $sqlCredentials = $null,
        [Parameter(Mandatory = $false)]
        [Parameter(Mandatory = $false,
            ParameterSetName = 'SQLServer')]
            [boolean] $sqlReplaceExternalDBs = $false,
        [Parameter(Mandatory = $false)]
        [ValidatePattern("^(\d+)(GB|gb)$")]
        [string] $memoryLimit = '12g',
        [switch] $singleTenant,
        [switch] $skipAlwaysPull,
        [switch] $SetupTestUsers,
        [switch] $AddTestTool,
        [switch] $skipBackup,
        [switch] $useHyperVIsolation,
        [switch] $enableTaskScheduler,
        [switch] $disableHttps,
        [switch] $forceRebuild
    )

    if ($PSCmdlet.ShouldProcess("Container", "This will create a new ")) {

        Write-Output @'

         _____ _ _____ _
        | __ \ (_) | __ \ | |
        | |__) | __ ___ _ __ __ _ _ __ _ _ __ __ _ | |__) |_ _ _ __ __ _ _ __ ___ ___| |_ ___ _ __ ___
        | ___/ '__/ _ \ '_ \ / _` | '__| | '_ \ / _` | | ___/ _` | '__/ _` | '_ ` _ \ / _ \ __/ _ \ '__/ __|
        | | | | | __/ |_) | (_| | | | | | | | (_| | | | | (_| | | | (_| | | | | | | __/ || __/ | \__ \
        |_| |_| \___| .__/ \__,_|_| |_|_| |_|\__, | |_| \__,_|_| \__,_|_| |_| |_|\___|\__\___|_| |___/
                       | | __/ |
                       |_| |___/

'@


        $os = (Get-CimInstance Win32_OperatingSystem)
        $isServerHost = $os.ProductType -eq 3
        $useSSL = !$disableHttps.IsPresent

        if (!$useHyperVIsolation.IsPresent -and !$isServerHost -and $os.BuildNumber -eq 22621 -and $useSSL) {
            Write-Output "Disabling SSL due to a bug in Windows 11"
            $useSSL = $false
        }

        $settings = Import-Config
        Set-ProperContext -settings $settings | Out-Null
        $SourcePath = Get-Location

        $dockerImageName = ""
        $ContainerName = Get-NewContainerName -SourcePath $SourcePath -ContainerName $ContainerName

        if ($null -eq $Credential) {
            $NewCredential = Get-CredentialFromEnvironmentJson -SourcePath $SourcePath
            $Credential = $NewCredential
        }
        $Password = $Credential.Password

        $ArtifactUrl = Get-ProperArtifactUrl -Version ([ref]$Version) -Cu $CU -Country ([ref]$country) -ArtifactUrl $ArtifactUrl -target ([ref]$target) -imageName ([ref]$dockerImageName) -SourcePath $SourcePath

        if ($LicenseFile -eq "") {
            $LicenseFile = Get-LicenseFile -Publisher (Get-AppKeyValue -SourcePath $SourcePath -KeyName 'Publisher') -Version $Version -Settings $settings
        }

        Write-Output "Version: $Version"
        if ($CU -ne "" -and $null -ne $CU) {
            Write-Output "Cumulative Update: $CU"
        }
        Write-Output "Country: $Country"

        Write-Output "Artifact Url: $artifactUrl"
        Write-Output "Image: $dockerImageName"

        [version]$platform = Get-VersionAppJson -SourcePath $SourcePath -ArtifactUrl $ArtifactUrl
        if ($platform.Major -lt 15) {
            $skipBackup = $true
        }
        #disable using backup, since this is currently possibly causing issues as well
        $skipBackup = $true

        if (!$skipBackup.IsPresent) {
            if (!(Test-Path "C:\.backups")) {
                New-Item -Path "C:\.backups" -ItemType Directory | Out-Null
            }

            if ($Country -eq "") {
                $Country = Get-EnvironmentKeyValue -KeyName "locale" -SourcePath $SourcePath
            }

            $bakFolder = (Join-Path "C:\.backups" $platform)
            $bakFolder = (Join-Path $bakFolder $Country)

            if (Test-Path (Join-Path $bakFolder "*")) {
                Write-Output "Backups: Yes"
            }
        }
        else {
            Write-Output "Backups: No"
        }

        if ($sqlServer -ne "") {
            Write-Output "Using SQL Server: $sqlServer"
            Write-Output " SQL instance: $sqlInstance"
            Write-Output " Database: $sqlDatabase"
        }

        $customLicenseFile = Get-FileFromStorage -fileName $LicenseFile

        # custom scripts to fix different BcContainerHelper issues
        $startParameters = @{
            accept_eula              = $true
            accept_outdated          = $true
            updateHosts              = $true
            useBestContainerOS       = $true
            doNotExportObjectsToText = $true
            forceRebuild             = $forceRebuild.IsPresent
            useSSL                   = $useSSL
            auth                     = "UserPassword"
            containerName            = $ContainerName
            Credential               = $Credential
            artifactUrl              = $artifactUrl
            imageName                = $dockerImageName
            licenseFile              = $customLicenseFile
            memoryLimit              = $memoryLimit
            SQLMemoryLimit           = '50%'
            Timeout                  = 180
        }

        if ($useSSL -and $Version -eq "2016") {
            $startParameters.Add("myscripts", @("https://raw.githubusercontent.com/microsoft/nav-docker/master/override/SelfSignedCertificateEx/SetupCertificate.ps1"))
        }

        $localBackup = ""
        if ($databaseBackup -ne "") {
            $localBackup = Get-FileFromStorage -fileName $databaseBackup
            if ((Split-Path $localBackup -Leaf).Split('.')[-1].ToLower() -eq 'bak') {
                $startParameters.Add('bakFile', $localBackup)
            }
        }

        if ($useHyperVIsolation.IsPresent) {
            $startParameters.Add('isolation', 'HyperV')
        }

        if (Get-EnvironmentKeyValue -KeyName "usePremium" -SourcePath $SourcePath) {
            $startParameters.Add('assignPremiumPlan', $true)
        }

        $isStandardTesting = Get-UseStandardTest -MajorVersion $platform.Major -SourcePath $SourcePath
        if ($AddTestTool.IsPresent) {
            if ($isStandardTesting) {
                $startParameters.Add('includeTestToolkit', $true)
            }
        }

        if ($platform.Major -le 14) {
            $startParameters.Add('shortcuts', 'DesktopFolder')
            # Turn off symbol loading for now
            # $startParameters.Add('enableSymbolLoading', $false)
        }
        else {
            $startParameters.Add('shortcuts', 'None')
            $startParameters.Add('enableSymbolLoading', $false)
        }

        if (!$skipAlwaysPull.IsPresent) {
            $startParameters.Add('alwaysPull', $true)
        }

        if ($platform.Major -le 14) {
            $startParameters.Add('includeCSide', $true)
            $startParameters.Add('clickonce', $true)
        }

        if (!$skipBackup.IsPresent) {
            $startParameters.Add('bakFolder', $bakFolder)
            $startParameters.Add('additionalParameters', @("-v C:\.backups:C:\.backups"))
        }

        if ($enableTaskScheduler.IsPresent) {
            $startParameters.Add('enableTaskScheduler', $true)
        }
        else {
            $startParameters.Add('enableTaskScheduler', $false)
        }

        if ($singleTenant.IsPresent) {
            $startParameters.Add('multitenant', $false)
        }

        # turn off health check for CPU usage
        $startParameters.Add('doNotCheckHealth', $true)

        # DNS fix
        $startParameters.Add('dns', '8.8.8.8')

        # Add Sql Server
        if ($sqlServer -ne "") {
            $startParameters.Add('databaseServer', $sqlServer)
        }
        if ($sqlInstance -ne "") {
            $startParameters.Add('databaseInstance', $sqlInstance)
        }
        if ($sqlDatabase -ne "") {
            $startParameters.Add('databaseName', $sqlDatabase)
            $startParameters.Add('replaceExternalDatabases', $sqlReplaceExternalDBs)
            $startParameters.Add('databasePrefix', "$($ContainerName)-")
        }
        if ($null -ne $sqlCredentials) {
            $startParameters.Add('databaseCredential', $sqlCredentials)
        }
        # Containers are now created as multitenant. Turn this off for anything but the newer versions (greater or equal BC15)
        if ($platform.Major -lt 15) {
            $startParameters.Add('multitenant', $false)
        }

        if ($SetupTestUsers.IsPresent) {
            $startParameters.Add('finalizeDatabasesScriptBlock', {Setup-BcContainerTestUsers -containerName $ContainerName -password $Password -credential $Credential})
        }

        Write-Output "Container creation parameters:"
        $startParameters.GetEnumerator() | ForEach-Object {
            Write-Output (" {0}: {1}" -f $_.Key, $_.Value)
        }

        Write-Output @'

         _____ _ _ _____ _ _
        / ____| | | (_) / ____| | | (_)
       | | _ __ ___ __ _| |_ _ _ __ __ _ | | ___ _ __ | |_ __ _ _ _ __ ___ _ __
       | | | '__/ _ \/ _` | __| | '_ \ / _` | | | / _ \| '_ \| __/ _` | | '_ \ / _ \ '__|
       | |____| | | __/ (_| | |_| | | | | (_| | | |___| (_) | | | | || (_| | | | | | __/ |
        \_____|_| \___|\__,_|\__|_|_| |_|\__, | \_____\___/|_| |_|\__\__,_|_|_| |_|\___|_|
                                           __/ |
                                          |___/

'@


        Flush-ContainerHelperCache -cache bcartifacts -keepDays 7

        New-BcContainer @startParameters

        if ($localBackup -ne "") {
            if ((Split-Path $localBackup -Leaf).Split('.')[-1].ToLower() -eq 'bacpac') {
                $containerPath = Join-Path "C:\Run\My" (Split-Path $localBackup -Leaf)
                Copy-FileToBcContainer -containerName $ContainerName -localPath $localBackup -containerPath $containerPath

                Invoke-ScriptInBcContainer -containerName $ContainerName -scriptblock { Param($containerPath, $database)
                    Invoke-SqlCmd `
                    -Query "EXEC sp_configure 'contained', 1;RECONFIGURE;" `
                    -ServerInstance "localhost\SQLEXPRESS"
                    Restore-BacpacWithRetry `
                    -bacpac $containerPath `
                    -databasename $database `
                    -maxattempts 1
                } -argumentList $containerPath, $ContainerName

                $tenantId = 'default'

                if ($singleTenant.IsPresent) {
                    Invoke-ScriptInBcContainer -containerName $ContainerName -scriptblock { Param($database, $serverInstance)
                        Set-NAVServerConfiguration `
                            -ServerInstance $serverInstance `
                            -KeyName DatabaseName `
                            -KeyValue $database
                        Restart-NAVServerInstance `
                            -ServerInstance $serverInstance
                        Sync-NavTenant `
                            -ServerInstance $serverInstance `
                            -Force
                    } -argumentList $ContainerName, ((Get-BcContainerServerConfiguration -ContainerName $ContainerName).ServerInstance)
                }
                else {
                    $tenantId = $ContainerName
                    Invoke-ScriptInBCContainer -containerName $containerName -scriptblock { Param($database, $tenantId, $serverInstance)
                        Mount-NavTenant `
                            -ServerInstance $serverInstance `
                            -id $tenantId `
                            -databasename $database `
                            -databaseserver localhost `
                            -databaseinstance SQLEXPRESS `
                            -EnvironmentType Sandbox `
                            -OverwriteTenantIdInDatabase `
                            -Force
                        Sync-NavTenant `
                            -ServerInstance $ServerInstance `
                            -Tenant $tenantId `
                            -Force
                    } -argumentList $ContainerName, $tenantId, ((Get-BcContainerServerConfiguration -ContainerName $ContainerName).ServerInstance)
                }

                New-BcContainerBcUser `
                    -containerName $containerName `
                    -Credential $Credential `
                    -ChangePasswordAtNextLogOn:$false `
                    -PermissionSetId SUPER `
                    -tenant $tenantId
            }
        }

# Write-Output @'
#
# _____ _ _ _ _
# |_ _| | | (_) | | (_)
# | | _ __ ___ _ __ ___ _ __| |_ _ _ __ __ _ | | _ ___ ___ _ __ ___ ___
# | | | '_ ` _ \| '_ \ / _ \| '__| __| | '_ \ / _` | | | | |/ __/ _ \ '_ \/ __|/ _ \
# _| |_| | | | | | |_) | (_) | | | |_| | | | | (_| | | |____| | (_| __/ | | \__ \ __/
# |_____|_| |_| |_| .__/ \___/|_| \__|_|_| |_|\__, | |______|_|\___\___|_| |_|___/\___|
# | | __/ |
# |_| |___/
#
#'@
#
# Import-BcContainerLicense -containerName $ContainerName -licenseFile $customLicenseFile -restart

        if ($platform.Major -le 11) {
            Write-Output @'

             _ _ _ _ _ _ _ _____
            | | | | | | | | (_) /\ | | | | |_ _|
            | | | |_ __ __| | __ _| |_ _ _ __ __ _ / \ __| | __| |______| | _ __ ___
            | | | | '_ \ / _` |/ _` | __| | '_ \ / _` | / /\ \ / _` |/ _` |______| | | '_ \/ __|
            | |__| | |_) | (_| | (_| | |_| | | | | (_| | / ____ \ (_| | (_| | _| |_| | | \__ \
             \____/| .__/ \__,_|\__,_|\__|_|_| |_|\__, | /_/ \_\__,_|\__,_| |_____|_| |_|___/
                   | | __/ |
                   |_| |___/

'@


            Invoke-ScriptInBcContainer -containerName $ContainerName -scriptblock {
                $sharedPath = "C:\navpfiles\" + $args[0] + "0\RoleTailored Client\"
                $programPath = "C:\Program Files (x86)\Microsoft Dynamics NAV\" + $args[0] + "0\RoleTailored Client\"

                Write-Output "Copying add-in files to shared files"
                Copy-Item "C:\Program Files\Microsoft Dynamics NAV\*\Service\Add-Ins\" $sharedPath -recurse -Force
                Write-Output "Copying add-in files to program files"
                Copy-Item "C:\Program Files\Microsoft Dynamics NAV\*\Service\Add-Ins\" $programPath -Recurse -Force
            } -ArgumentList $platform.Major
        }

        if ($AddTestTool.IsPresent)
        {
            if (!$isStandardTesting)
            {
                Write-Output @'

                 _____ _ _______ _ _
                |_ _| | | |__ __| | | (_)
                  | | _ __ ___ _ __ ___ _ __| |_ | | ___ ___| |_ _ _ __ __ _
                  | | | '_ ` _ \| '_ \ / _ \| '__| __| | |/ _ \/ __| __| | '_ \ / _` |
                 _| |_| | | | | | |_) | (_) | | | |_ | | __/\__ \ |_| | | | | (_| |
                |_____|_| |_| |_| .__/ \___/|_| \__| |_|\___||___/\__|_|_| |_|\__, |
                                | | __/ |
                                |_| |___/

'@


                Import-Testing -containerName $ContainerName
            }
        }

        if ($platform.Major -ge 15) {
            Wait-ForTenantReady -containerName $ContainerName -tenant "default"
        }
    }
}
Export-ModuleMember New-Container