In my earlier posts I showed how to create the databases and how to install Dynamics NAV on the server machines.

The next step is to create an instance for every application and mount the default tenant.  The default tenant is the only one that will be able to change the application data.

To finish this demonstration I will show how I mount an empty tenant database and create a new company with clickonce installation for windows client and an active web client.

For each installation machine I create a separate folder. Within that folder I create a settings file, Set-MachineSettings.ps1

[code lang=”powershell”]$NAV_RemoteMachineAddress = ‘kl4msas37’
$ClientServicesCredentialType = ‘Windows’
[/code]

On my out-facing machine the client credential type is ‘NavUserPassword’. My second settings file is for the instance, Set-Instancesettings.ps1

[code lang=”powershell”]$dbNamePrefix = ‘NAV71_L01_’
$ServiceInstance = $dbNamePrefix + ‘APP’
$DatabaseName = $ServiceInstance
$SQL_DatabaseInstance = ”
$SQL_RemoteMachineAddress = ‘SQLSERVER’
$ManagementServicesPort = 7100
$NAV_WindowsServiceDomain = ‘KAPPI’
$NAV_WindowsServiceAccount = ‘NAVSERVICE’
$NAV_WindowsServiceAccountPassword = ‘NavPass@Word’
$ClientServicesPort = $ManagementServicesPort + 1
$SOAPServicesPort = $ClientServicesPort + 1
$ODataServicesPort = $SOAPServicesPort + 1
$ClickOnceWebSitePort = 80
$DefTenant = $dbNamePrefix + ‘DEF’
$DemoName = ‘www.kappi.is’
$OtherTenants = ”[/code]

With these setting I can create the instance remotly and change the settings to multitenant.

[code lang=”powershell”]Set-StrictMode -Version 2.0

Import-Module ‘N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministrationSamples\NAVRemoteAdministrationSamples.psm1’ -DisableNameChecking
Import-Module ‘N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\Misc\Import-NAVAdministrationModuleRemotely.ps1’ -DisableNameChecking

# Import settings
$PSScriptRootV2 = Split-Path $MyInvocation.MyCommand.Definition -Parent
. (Join-Path $PSScriptRootV2 ‘..\Set-DeploySettings.ps1’)
. (Join-Path $PSScriptRootV2 ‘Set-MachineSettings.ps1’)
. (Join-Path $PSScriptRootV2 ‘..\Set-InstanceSettings.ps1’)

#New-NavAdminSession
[int]$currentMemoryLimitPerPSSessionInMB = Get-MaxMemoryPerShellRemotely -RemoteMachineAddress $NAV_RemoteMachineAddress
$requiredMemoryLimitPerSessionInMB = 1024
if(($currentMemoryLimitPerPSSessionInMB -ne 0) -and ($currentMemoryLimitPerPSSessionInMB -lt $requiredMemoryLimitPerSessionInMB))
{
Set-MaxMemoryPerShellRemotely -Value $requiredMemoryLimitPerSessionInMB -RemoteMachineAddress $NAV_RemoteMachineAddress
}

Write-Verbose "Creating remote PS session on $NAV_RemoteMachineAddress…"
$Session = New-PSSession -ComputerName $NAV_RemoteMachineAddress
Write-Verbose "Done creating remote PS session on $NAV_RemoteMachineAddress."

try {
# Import the module into the remote PS session
Invoke-Command -Session $Session -ScriptBlock {
param([string] $ModulePath, [string] $verbosePreference)
$VerbosePreference = $verbosePreference
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Import-Module $ModulePath
Add-PSSnapin "Microsoft.Dynamics.Nav.Management" -ErrorAction SilentlyContinue
} -ArgumentList (Join-Path $NAV_AdminRemoteDirectory ‘NAVAdministration.psm1’), $VerbosePreference
Write-Verbose "================================================================================"
Write-Verbose ("Deploy-NewInstance starting at " + (Get-Date).ToLongTimeString() + "…")
Write-Verbose "================================================================================"

New-NAVServerInstanceRemotely -ServiceInstance $ServiceInstance -DatabaseName $DatabaseName -DatabaseInstance $SQL_DatabaseInstance -DatabaseServer $SQL_RemoteMachineAddress -ManagementServicesPort $ManagementServicesPort -ClientServicesPort $ClientServicesPort -SOAPServicesPort $SOAPServicesPort -ODataServicesPort $ODataServicesPort -RemoteMachineShortName $NAV_WindowsServiceDomain -ServiceAccount $NAV_WindowsServiceAccount -ServiceAccountPassword $NAV_WindowsServiceAccountPassword -Session $Session
Set-NAVServerConfigurationRemotely -KeyName "ClientServicesCredentialType" -KeyValue $ClientServicesCredentialType -ServerInstance $ServiceInstance -Session $Session
Set-NAVServerConfigurationRemotely -KeyName "MultiTenant" -KeyValue "true" -ServerInstance $ServiceInstance -Session $Session
Set-NAVServerConfigurationRemotely -KeyName "DatabaseName" -KeyValue "" -ServerInstance $ServiceInstance -Session $Session
Set-NAVServerConfigurationRemotely -KeyName ‘NASServicesStartupCodeunit’ -KeyValue ‘450’ -ServerInstance $ServiceInstance -Session $Session
Set-NAVServerConfigurationRemotely -KeyName ‘NASServicesStartupMethod’ -KeyValue ” -ServerInstance $ServiceInstance -Session $Session
Set-NAVServerConfigurationRemotely -KeyName ‘NASServicesStartupArgument’ -KeyValue ‘JOBQUEUE’ -ServerInstance $ServiceInstance -Session $Session

$fullAccountName = $NAV_WindowsServiceDomain + "\" + $NAV_WindowsServiceAccount

# Install certificates
[System.Security.SecureString]$clientServicesPfxPasswordAsSecureString = ConvertTo-SecureString $ClientServicesPfxPassword -AsPlainText -Force
if ($ClientServicesPfxFile -ne $null)
{
Install-ClientServicesCertificate
-ServiceInstance $ServiceInstance

-ServiceAccount $fullAccountName
-ClientServicesPfxFile $ClientServicesPfxFile

-ClientServicesPfxPassword $clientServicesPfxPasswordAsSecureString
-Session $Session
}

Start-ServiceRemotely -ServiceName ("MicrosoftDynamicsNavServer$" + $ServiceInstance) -Session $Session

Write-Verbose "================================================================================"
Write-Verbose ("Deploy-NewInstance finished at " + (Get-Date).ToLongTimeString() + ".")
Write-Verbose "================================================================================"

Write-Output "The NAV Server virtual machine is: $NAV_RemoteMachineAddress"
Write-Output "The NAV Server Instance is: $ServiceInstance"
Write-Output "The NAV Server account credentials: $NAV_WindowsServiceDomain\$NAV_WindowsServiceAccount/$NAV_WindowsServiceAccountPassword"

}
catch [System.Exception] {
Remove-NAVAdminSession -Session $Session
throw $_.Exception
}

finally
{
Remove-NAVAdminSession -Session $Session
}

[/code]

I also activate the JOBQUEUE NAS function by default.
I use the CRONUS database as the default tenant and that is the only one with write access to the application database. My next step is to mount the application and the default tenant and add required users to the default tenant.

[code lang="powershell"]Set-StrictMode -Version 2.0

Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministrationSamples\NAVRemoteAdministrationSamples.psm1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\Misc\Import-NAVAdministrationModuleRemotely.ps1' -DisableNameChecking

# Import settings
$PSScriptRootV2 = Split-Path $MyInvocation.MyCommand.Definition -Parent
. (Join-Path $PSScriptRootV2 '..\Set-DeploySettings.ps1')
. (Join-Path $PSScriptRootV2 'Set-MachineSettings.ps1')
. (Join-Path $PSScriptRootV2 '..\Set-InstanceSettings.ps1')

#New-NavAdminSession
[int]$currentMemoryLimitPerPSSessionInMB = Get-MaxMemoryPerShellRemotely -RemoteMachineAddress $NAV_RemoteMachineAddress
$requiredMemoryLimitPerSessionInMB = 1024
if(($currentMemoryLimitPerPSSessionInMB -ne 0) -and ($currentMemoryLimitPerPSSessionInMB -lt $requiredMemoryLimitPerSessionInMB))
{
Set-MaxMemoryPerShellRemotely -Value $requiredMemoryLimitPerSessionInMB -RemoteMachineAddress $NAV_RemoteMachineAddress
}

Write-Verbose "Creating remote PS session on $NAV_RemoteMachineAddress..."
$Session = New-PSSession -ComputerName $NAV_RemoteMachineAddress
Write-Verbose "Done creating remote PS session on $NAV_RemoteMachineAddress."

try {
# Import the module into the remote PS session
Invoke-Command -Session $Session -ScriptBlock {
param([string] $ModulePath, [string] $verbosePreference)
$VerbosePreference = $verbosePreference
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Import-Module $ModulePath
Add-PSSnapin "Microsoft.Dynamics.Nav.Management" -ErrorAction SilentlyContinue
} -ArgumentList (Join-Path $NAV_AdminRemoteDirectory 'NAVAdministration.psm1'), $VerbosePreference

Write-Verbose "Mount Application to $ServiceInstance..."
Invoke-Command -Session $session -ScriptBlock

{
param([string]$ServiceInstance, [string]$DatabaseServer, [string]$DatabaseInstance, [string]$DatabaseName)
Mount-NAVApplication -ServerInstance $ServiceInstance -DatabaseServer $DatabaseServer -DatabaseInstance $DatabaseInstance -DatabaseName $DatabaseName

}
-ArgumentList $ServiceInstance, $SQL_RemoteMachineAddress, $SQL_DatabaseInstance, $DatabaseName

Write-Verbose "Mount Default Tenant to $ServiceInstance..."
Invoke-Command -Session $session -ScriptBlock

{
param([string]$ServiceInstance, [string]$DatabaseServer, [string]$DatabaseInstance, [string]$DatabaseName, [string]$AlternateId)
Mount-NAVTenant -ServerInstance $ServiceInstance -DatabaseServer $DatabaseServer -DatabaseInstance $DatabaseInstance -DatabaseName $DatabaseName -Id Default -OverwriteTenantIdInDatabase -AllowAppDatabaseWrite -AlternateId $AlternateId

}
-ArgumentList $ServiceInstance, $SQL_RemoteMachineAddress, $SQL_DatabaseInstance, $DefTenant, $DemoName

[System.Security.SecureString]$NAVPasswordAsSecureString = ConvertTo-SecureString $NAVAdminPassword -AsPlainText -Force
New-NAVServerUserRemotely -UserName $NAVAdminUserName -Password $NAVPasswordAsSecureString -ServerInstance $ServiceInstance -Tenant Default -Session $Session
New-NAVServerUserPermissionSetRemotely -UserName $NAVAdminUserName -PermissionSetId "SUPER" -ServerInstance $ServiceInstance -Tenant Default -Session $Session

$fullUserName = $NAV_WindowsServiceDomain + "\" + $NAV_WindowsServiceAccount
New-NAVServerUserRemotely -WindowsAccount $fullUserName -ServerInstance $ServiceInstance -Tenant Default -Session $Session
New-NAVServerUserPermissionSetRemotely -UserName $fullUserName -PermissionSetId "SUPER" -ServerInstance $ServiceInstance -Tenant Default -Session $Session

$fullUserName = $NAV_WindowsServiceDomain + "\kappi"
New-NAVServerUserRemotely -WindowsAccount $fullUserName -ServerInstance $ServiceInstance -Tenant Default -Session $Session
New-NAVServerUserPermissionSetRemotely -UserName $fullUserName -PermissionSetId "SUPER" -ServerInstance $ServiceInstance -Tenant Default -Session $Session

if ($OtherTenants -ne '') {
foreach ($tenant in $OtherTenants)
{
$tenantdbName = $dbNamePrefix + $tenant
Write-Verbose "Mount Tenant $tenant to $ServiceInstance..."
Invoke-Command -Session $session -ScriptBlock

{
param([string]$ServiceInstance, [string]$DatabaseServer, [string]$DatabaseInstance, [string]$TenantId, [string]$DatabaseName)
Mount-NAVTenant -ServerInstance $ServiceInstance -DatabaseServer $DatabaseServer -DatabaseInstance $DatabaseInstance -DatabaseName $DatabaseName -Id $TenantId -OverwriteTenantIdInDatabase

}
-ArgumentList $ServiceInstance, $SQL_RemoteMachineAddress, $SQL_DatabaseInstance, $tenant, $tenantdbName

}
}

Write-Verbose "Done Mounting Tenants to Server Instance $ServiceInstance on $NAV_RemoteMachineAddress."

}
catch [System.Exception] {
Remove-NAVAdminSession -Session $Session
throw $_.Exception
}

finally
{
Remove-NAVAdminSession -Session $Session
}

[/code]

If something when wrong I can use this script to remote the instance and try again.

[code lang="powershell"]Set-StrictMode -Version 2.0

Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministrationSamples\NAVRemoteAdministrationSamples.psm1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\Misc\Import-NAVAdministrationModuleRemotely.ps1' -DisableNameChecking

# Import settings
$PSScriptRootV2 = Split-Path $MyInvocation.MyCommand.Definition -Parent
. (Join-Path $PSScriptRootV2 '..\Set-DeploySettings.ps1')
. (Join-Path $PSScriptRootV2 'Set-MachineSettings.ps1')
. (Join-Path $PSScriptRootV2 '..\Set-InstanceSettings.ps1')

#New-NavAdminSession
[int]$currentMemoryLimitPerPSSessionInMB = Get-MaxMemoryPerShellRemotely -RemoteMachineAddress $NAV_RemoteMachineAddress
$requiredMemoryLimitPerSessionInMB = 1024
if(($currentMemoryLimitPerPSSessionInMB -ne 0) -and ($currentMemoryLimitPerPSSessionInMB -lt $requiredMemoryLimitPerSessionInMB))
{
Set-MaxMemoryPerShellRemotely -Value $requiredMemoryLimitPerSessionInMB -RemoteMachineAddress $NAV_RemoteMachineAddress
}

Write-Verbose "Creating remote PS session on $NAV_RemoteMachineAddress..."
$Session = New-PSSession -ComputerName $NAV_RemoteMachineAddress
Write-Verbose "Done creating remote PS session on $NAV_RemoteMachineAddress."

try {
# Import the module into the remote PS session
Invoke-Command -Session $Session -ScriptBlock {
param([string] $ModulePath, [string] $verbosePreference)
$VerbosePreference = $verbosePreference
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Import-Module $ModulePath
Add-PSSnapin "Microsoft.Dynamics.Nav.Management" -ErrorAction SilentlyContinue
} -ArgumentList (Join-Path $NAV_AdminRemoteDirectory 'NAVAdministration.psm1'), $VerbosePreference

Write-Verbose "Removing Server Instance $ServiceInstance..."
Invoke-Command -Session $session -ScriptBlock

{
param([string]$ServiceInstance)
Remove-NAVServerInstance -ServerInstance $ServiceInstance -Force

}
-ArgumentList $ServiceInstance

Write-Verbose "Done Removing Server Instance $ServiceInstance on $NAV_RemoteMachineAddress."

}
catch [System.Exception] {
Remove-NAVAdminSession -Session $Session
throw $_.Exception
}

finally
{
Remove-NAVAdminSession -Session $Session
}

[/code]

All the framework is installed and running. Now it is time to prepare the system for my first client. I create a folder for each client and in that folder I put the Set-TenantSettings.ps1 file.

[code lang="powershell"]# Select Instance
. (Join-Path $PSScriptRootV2 '..\Set-InstanceSettings.ps1')

# Install on machines
$InstallOnMachines = ('PUBLICSERVER','LOCALSERVER','DEVSERVER')

# Next available Tenant
$TenantId = '01'
# Domain name for ClickOnce and Web Client
$AlternateIds = @("microsoft.kappi.is")
$EndpointIdentifier = 'microsoft.kappi.is'

# New Company Name
$NewCompanyName = 'Microsoft'

# Activate NAS
$EnableNAS = '$true'

# Web Client Path
$WebServerInstance = 'NAV71_MS'

# ClickOnce Client Application Name - No need to change
$applicationName = "Microsoft Dynamics NAV 2013 R2 for $NewCompanyName"

# First Super User
$ClientUserName = 'Gunnar'
$ClientUserPassword = 'gunnar@dynamics.is'
[/code]

Now I am all set to mount my first tenant and create the first company. This script will also create the users needed for the tenant.

[code lang="powershell"]Set-StrictMode -Version 2.0

Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministrationSamples\NAVRemoteAdministrationSamples.psm1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\Misc\Import-NAVAdministrationModuleRemotely.ps1' -DisableNameChecking

# Import settings
$PSScriptRootV2 = Split-Path $MyInvocation.MyCommand.Definition -Parent
. (Join-Path $PSScriptRootV2 '..\Set-DeploySettings.ps1')
. (Join-Path $PSScriptRootV2 'Set-TenantSettings.ps1')

$LoopCount = 0

# Loop Machines
foreach ($InstallOnMachine in $InstallOnMachines)
{
$MachineSettingsPath = '..\' + $InstallOnMachine + '\Set-MachineSettings.ps1'
. (Join-Path $PSScriptRootV2 $MachineSettingsPath)
$LoopCount = $LoopCount + 1

#New-NavAdminSession
[int]$currentMemoryLimitPerPSSessionInMB = Get-MaxMemoryPerShellRemotely -RemoteMachineAddress $NAV_RemoteMachineAddress
$requiredMemoryLimitPerSessionInMB = 1024
if(($currentMemoryLimitPerPSSessionInMB -ne 0) -and ($currentMemoryLimitPerPSSessionInMB -lt $requiredMemoryLimitPerSessionInMB))
{
Set-MaxMemoryPerShellRemotely -Value $requiredMemoryLimitPerSessionInMB -RemoteMachineAddress $NAV_RemoteMachineAddress
}

Write-Verbose "Creating remote PS session on $NAV_RemoteMachineAddress..."
$Session = New-PSSession -ComputerName $NAV_RemoteMachineAddress
Write-Verbose "Done creating remote PS session on $NAV_RemoteMachineAddress."

try {
# Import the module into the remote PS session
Invoke-Command -Session $Session -ScriptBlock {
param([string] $ModulePath, [string] $verbosePreference)
$VerbosePreference = $verbosePreference
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Import-Module $ModulePath
Add-PSSnapin "Microsoft.Dynamics.Nav.Management" -ErrorAction SilentlyContinue
} -ArgumentList (Join-Path $NAV_AdminRemoteDirectory 'NAVAdministration.psm1'), $VerbosePreference

$DatabaseName = $dbNamePrefix + $TenantId
Invoke-Command -Session $Session -ScriptBlock

{
param([string]$TenandId, [string]$ServiceInstance, [string]$DatabaseName, [string]$DatabaseInstance, [string]$DatabaseServer, [string]$AlternateId, [string]$DefaultCompanyName, [string]$EnableNAS, [int]$LoopCount)
if ($EnableNAS -eq ‘$true’)
{
Mount-NAVTenant -Id $TenandId -ServerInstance $ServiceInstance -DatabaseName $DatabaseName -DatabaseServer $DatabaseServer -DatabaseInstance $DatabaseInstance -AlternateId $AlternateId -DefaultCompany $DefaultCompanyName -NasServicesEnabled -OverwriteTenantIdInDatabase
}
else
{
Mount-NAVTenant -Id $TenandId -ServerInstance $ServiceInstance -DatabaseName $DatabaseName -DatabaseServer $DatabaseServer -DatabaseInstance $DatabaseInstance -AlternateId $AlternateId -DefaultCompany $DefaultCompanyName -OverwriteTenantIdInDatabase
}
if ($LoopCount -eq 1)
{
New-NAVCompany -ServerInstance $ServiceInstance -Tenant $TenandId -CompanyName $DefaultCompanyName
}
}
-ArgumentList $TenantId, $ServiceInstance, $DatabaseName, $SQL_DatabaseInstance, $SQL_RemoteMachineAddress, $AlternateIds, $NewCompanyName, $EnableNAS, $LoopCount

if ($LoopCount -eq 1)
{
[System.Security.SecureString]$NAVPasswordAsSecureString = ConvertTo-SecureString $NAVAdminPassword -AsPlainText -Force
New-NAVServerUserRemotely -UserName $NAVAdminUserName -Password $NAVPasswordAsSecureString -ServerInstance $ServiceInstance -Tenant $TenantId -Session $Session -ChangePasswordAtNextLogOn $false
New-NAVServerUserPermissionSetRemotely -UserName $NAVAdminUserName -PermissionSetId "SUPER" -ServerInstance $ServiceInstance -Tenant $TenantId -Session $Session

$fullUserName = $NAV_WindowsServiceDomain + "\" + $NAV_WindowsServiceAccount
New-NAVServerUserRemotely -WindowsAccount $fullUserName -ServerInstance $ServiceInstance -Tenant $TenantId -Session $Session
New-NAVServerUserPermissionSetRemotely -UserName $fullUserName -PermissionSetId "SUPER" -ServerInstance $ServiceInstance -Tenant $TenantId -Session $Session

[System.Security.SecureString]$ClientUserPasswordAsSecureString = ConvertTo-SecureString $ClientUserPassword -AsPlainText -Force
New-NAVServerUserRemotely -UserName $ClientUserName -Password $ClientUserPasswordAsSecureString -ServerInstance $ServiceInstance -Tenant $TenantId -Session $Session -ChangePasswordAtNextLogOn $true
New-NAVServerUserPermissionSetRemotely -UserName $NAVAdminUserName -PermissionSetId "SUPER" -ServerInstance $ServiceInstance -Tenant $TenantId -Session $Session
}
}
catch [System.Exception] {
Remove-NAVAdminSession -Session $Session
throw $_.Exception
}

finally
{
Remove-NAVAdminSession -Session $Session
}

}[/code]

All set and now to create the client distribution with clickonce.

[code lang="powershell"]Set-StrictMode -Version 2.0

Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministrationSamples\NAVRemoteAdministrationSamples.psm1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\Misc\Import-NAVAdministrationModuleRemotely.ps1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\ClickOnce\Test-MageExePrerequisite.ps1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\ClickOnce\Get-MageExeLocation.ps1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\ClickOnce\Set-ManifestSignatureRemotely.ps1' -DisableNameChecking
Import-Module 'N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\ClickOnce\New-ClickOnceWebSiteRemotely.ps1' -DisableNameChecking

# Import settings
$PSScriptRootV2 = Split-Path $MyInvocation.MyCommand.Definition -Parent
. (Join-Path $PSScriptRootV2 '..\Set-DeploySettings.ps1')
. (Join-Path $PSScriptRootV2 '..\PUBLICSERVER\Set-MachineSettings.ps1')
. (Join-Path $PSScriptRootV2 '..\Set-InstanceSettings.ps1')
. (Join-Path $PSScriptRootV2 'Set-TenantSettings')

#New-NavAdminSession
[int]$currentMemoryLimitPerPSSessionInMB = Get-MaxMemoryPerShellRemotely -RemoteMachineAddress $NAV_RemoteMachineAddress
$requiredMemoryLimitPerSessionInMB = 1024
if(($currentMemoryLimitPerPSSessionInMB -ne 0) -and ($currentMemoryLimitPerPSSessionInMB -lt $requiredMemoryLimitPerSessionInMB))
{
Set-MaxMemoryPerShellRemotely -Value $requiredMemoryLimitPerSessionInMB -RemoteMachineAddress $NAV_RemoteMachineAddress
}

Write-Verbose "Creating remote PS session on $NAV_RemoteMachineAddress..."
$Session = New-PSSession -ComputerName $NAV_RemoteMachineAddress
Write-Verbose "Done creating remote PS session on $NAV_RemoteMachineAddress."

try {
# Import the module into the remote PS session
Invoke-Command -Session $Session -ScriptBlock {
param([string] $ModulePath, [string] $verbosePreference)
$VerbosePreference = $verbosePreference
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Import-Module $ModulePath
Add-PSSnapin "Microsoft.Dynamics.Nav.Management" -ErrorAction SilentlyContinue
} -ArgumentList (Join-Path $NAV_AdminRemoteDirectory 'NAVAdministration.psm1'), $VerbosePreference

# Create a web site that holds a ClickOnce deployment of the Windows client
Write-Verbose "Creating a web site that holds a ClickOnce deployment of the Windows client on $NAV_RemoteMachineAddress..."
[System.Security.SecureString]$clickOnceCodeSigningPfxPasswordAsSecureString = $null
if ($ClickOnceCodeSigningPfxPassword)
{
$clickOnceCodeSigningPfxPasswordAsSecureString = ConvertTo-SecureString $ClickOnceCodeSigningPfxPassword -AsPlainText -Force
}
$Port = $ClickOnceWebSitePort
$clickOnceDeploymentId = "ClickOnce_${ServiceInstance}_at_${EndpointIdentifier}"
$clickOnceDirectory = Join-Path 'C:\inetpub' $clickOnceDeploymentId
$webSiteUrl = ("http://" + $EndpointIdentifier + ":" + $Port)

Write-Verbose "Creating new ClickOnce web site for server instance $ServiceInstance at address $webSiteUrl..."

# Create and populate the directory on the remote machine
[xml]$clientUserSettings = New-NAVClientUserSettings

-Session $Session
-Server $EndpointIdentifier

-ServerInstance $ServiceInstance
-ClientServicesPort $ClientServicesPort

-DnsIdentity $dnsIdentity
Edit-NAVClientUserSettings -ClientUserSettings $ClientUserSettings  -KeyName ‘ClientServicesCredentialType’ -NewValue $ClientServicesCredentialType
Edit-NAVClientUserSettings -ClientUserSettings $ClientUserSettings  -KeyName ‘ServicesCertificateValidationEnabled’ -NewValue false
Edit-NAVClientUserSettings -ClientUserSettings $ClientUserSettings  -KeyName ‘TenantId’ -NewValue $TenantId

Invoke-Command -Session $session -ScriptBlock {
param([string]$ClickOnceDirectory,[xml]$ClientUserSettings)
New-ClickOnceDirectory
-ClickOnceDirectory $ClickOnceDirectory

-ClientUserSettings $ClientUserSettings
} -ArgumentList $clickOnceDirectory,$ClientUserSettings

# Copy the mage.exe file to the remote machine
if (!(Test-MageExePrerequisite))
{
Write-Error ‘Mage.exe not found or wrong version. Install the Windows SDK from http://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=8279, and set up the global variable MageExeFile ($Global:MageFileExe) to point to the executable, probably C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\NETFX 4.0 Tools\mage.exe.’
return $null
}
$localMageExeFile = Get-MageExeLocation
$remoteMageExeFile = Join-Path $NAV_RemoteFolder (Split-Path -Leaf $localMageExeFile)
Copy-FileToRemoteMachine -SourceFile $localMageExeFile -DestinationFile $remoteMageExeFile -Session $session

# Adjust the application manifest (Microsoft.Dynamics.Nav.Client.exe.manifest)
$clickOnceApplicationFilesDirectory = Join-Path $clickOnceDirectory ‘Deployment\ApplicationFiles’
$applicationManifestFile = Join-Path $clickOnceApplicationFilesDirectory ‘Microsoft.Dynamics.Nav.Client.exe.manifest’
$applicationIdentityName = "$clickOnceDeploymentId application identity" # any unique value will do
$applicationIdentityVersion = "1.0.0.0" # any version will do; it is not tied to the product version
Invoke-Command -Session $session -ScriptBlock {
param([string]$ApplicationManifestFile,[string]$ApplicationIdentityName,[string]$ApplicationIdentityVersion,[string]$ApplicationFilesDirectory,[string]$RemoteMageExeFile)

# Note: be careful to run "mage -update" before you change settings, because "mage -update" sometimes resets some of the other settings
Set-ApplicationManifestFileList
-ApplicationManifestFile $ApplicationManifestFile

-ApplicationFilesDirectory $ApplicationFilesDirectory
-MageExeLocation $RemoteMageExeFile
Set-ApplicationManifestApplicationIdentity

-ApplicationManifestFile $ApplicationManifestFile
-ApplicationIdentityName $ApplicationIdentityName

-ApplicationIdentityVersion $ApplicationIdentityVersion
} -ArgumentList $applicationManifestFile,$applicationIdentityName,$applicationIdentityVersion,$ClickOnceApplicationFilesDirectory,$remoteMageExeFile

# Sign the application manifest
if ($ClickOnceCodeSigningPfxFile)
{
Set-ManifestSignatureRemotely
-ManifestFile $applicationManifestFile

-CodeSigningPfxFile $ClickOnceCodeSigningPfxFile
-CodeSigningPfxPassword $clickOnceCodeSigningPfxPasswordAsSecureString

-MageExeLocation $localMageExeFile
-Session $Session
}

# Adjust the deployment manifest (Microsoft.Dynamics.Nav.Client.application)
$deploymentManifestFile = Join-Path $clickOnceDirectory 'Deployment\Microsoft.Dynamics.Nav.Client.application'
$deploymentIdentityName = "$clickOnceDeploymentId deployment identity" # any unique value will do
$deploymentIdentityVersion = "1.0.0.0" # any version will do; it is not tied to the product version
$deploymentManifestUrl = ($webSiteUrl + "/Deployment/Microsoft.Dynamics.Nav.Client.application")
$applicationManifestUrl = ($webSiteUrl + "/Deployment/ApplicationFiles/Microsoft.Dynamics.Nav.Client.exe.manifest")
Invoke-Command -Session $session -ScriptBlock {
param([string]$DeploymentManifestFile,[string]$ApplicationManifestFile,[string]$DeploymentIdentityName,[string]$DeploymentIdentityVersion,[string]$ApplicationPublisher,[string]$ApplicationName,[string]$DeploymentManifestUrl,[string]$ApplicationManifestUrl,[string]$RemoteMageExeFile)
# Note: be careful to run "mage -update" before you change settings, because "mage -update" sometimes resets some of the other settings
Set-DeploymentManifestApplicationReference

-DeploymentManifestFile $DeploymentManifestFile
-ApplicationManifestFile $ApplicationManifestFile

-ApplicationManifestUrl $ApplicationManifestUrl
-MageExeLocation $RemoteMageExeFile
Set-DeploymentManifestSettings

-DeploymentManifestFile $DeploymentManifestFile
-DeploymentIdentityName $DeploymentIdentityName

-DeploymentIdentityVersion $DeploymentIdentityVersion
-ApplicationPublisher $ApplicationPublisher

-ApplicationName $ApplicationName
-DeploymentManifestUrl $DeploymentManifestUrl
} -ArgumentList $deploymentManifestFile,$applicationManifestFile,$deploymentIdentityName,$deploymentIdentityVersion,$ApplicationPublisher,$ApplicationName,$deploymentManifestUrl,$applicationManifestUrl,$remoteMageExeFile

# Sign the deployment manifest
if ($ClickOnceCodeSigningPfxFile)
{
Set-ManifestSignatureRemotely

-ManifestFile $deploymentManifestFile
-CodeSigningPfxFile $ClickOnceCodeSigningPfxFile

-CodeSigningPfxPassword $clickOnceCodeSigningPfxPasswordAsSecureString
-MageExeLocation $localMageExeFile

-Session $Session
}

# Create the actual web site
Invoke-Command -Session $session -ScriptBlock {
param([string]$WebSiteName,[int]$Port,[string]$PhysicalPath, [string]$EndpointIdentifier, [string]$AdminRemoteDirectory)
Write-Verbose "Creating new web site $WebSiteName on port $Port, pointing to $PhysicalPath…"

# Create the web site
$website = New-Website -Name $WebSiteName -Port $Port -PhysicalPath $PhysicalPath -HostHeader $EndpointIdentifier -Force

# Put a web.config file in the root folder, which will tell IIS which .html file to open
$sourceFile = Join-Path $AdminRemoteDirectory ‘ClickOnce\Resources\root_web.config’
$targetFile = Join-Path $PhysicalPath ‘web.config’
Copy-Item $sourceFile -destination $targetFile

# Put a web.config file in the Deployment folder, which will tell IIS to allow downloading of .config files etc.
$sourceFile = Join-Path $AdminRemoteDirectory ‘ClickOnce\Resources\deployment_web.config’
$targetFile = Join-Path $PhysicalPath ‘Deployment\web.config’
Copy-Item $sourceFile -destination $targetFile

# Open firewall on the given port
New-FirewallPortAllowRule -RuleName $WebSiteName -Port $Port

Write-Verbose "Done creating new web site $WebSiteName on port $Port, pointing to $PhysicalPath."

} -ArgumentList $clickOnceDeploymentId,$Port,$clickOnceDirectory, $EndpointIdentifier, $NAV_AdminRemoteDirectory

Write-Verbose "Done creating new ClickOnce web site for server instance $ServiceInstance at address $webSiteUrl."
Write-Output "The Windows Client can be downloaded via ClickOnce at: $webSiteUrl"

}
catch [System.Exception] {
Remove-NAVAdminSession -Session $Session
throw $_.Exception
}

finally
{
Remove-NAVAdminSession -Session $Session
}

[/code]

And finally the web client.

[code lang=”powershell”]Set-StrictMode -Version 2.0

Import-Module ‘N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministrationSamples\NAVRemoteAdministrationSamples.psm1’ -DisableNameChecking
Import-Module ‘N:\NAV2013R2\NAVDVD\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\Misc\Import-NAVAdministrationModuleRemotely.ps1’ -DisableNameChecking

# Import settings
$PSScriptRootV2 = Split-Path $MyInvocation.MyCommand.Definition -Parent
. (Join-Path $PSScriptRootV2 ‘..\Set-DeploySettings.ps1’)
. (Join-Path $PSScriptRootV2 ‘..\PUBLICSERVER\Set-MachineSettings.ps1’)
. (Join-Path $PSScriptRootV2 ‘..\Set-InstanceSettings.ps1’)
. (Join-Path $PSScriptRootV2 ‘Set-TenantSettings’)

#New-NavAdminSession
[int]$currentMemoryLimitPerPSSessionInMB = Get-MaxMemoryPerShellRemotely -RemoteMachineAddress $NAV_RemoteMachineAddress
$requiredMemoryLimitPerSessionInMB = 1024
if(($currentMemoryLimitPerPSSessionInMB -ne 0) -and ($currentMemoryLimitPerPSSessionInMB -lt $requiredMemoryLimitPerSessionInMB))
{
Set-MaxMemoryPerShellRemotely -Value $requiredMemoryLimitPerSessionInMB -RemoteMachineAddress $NAV_RemoteMachineAddress
}

Write-Verbose "Creating remote PS session on $NAV_RemoteMachineAddress…"
$Session = New-PSSession -ComputerName $NAV_RemoteMachineAddress
Write-Verbose "Done creating remote PS session on $NAV_RemoteMachineAddress."

try {
# Import the module into the remote PS session
Invoke-Command -Session $Session -ScriptBlock {
param([string] $ModulePath, [string] $verbosePreference)
$VerbosePreference = $verbosePreference
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Import-Module $ModulePath
Add-PSSnapin "Microsoft.Dynamics.Nav.Management" -ErrorAction SilentlyContinue
} -ArgumentList (Join-Path $NAV_AdminRemoteDirectory ‘NAVAdministration.psm1’), $VerbosePreference

Invoke-Command -Session $Session -ScriptBlock
{
param([string]$ClientServicesCredentialType, [int]$ClientServicesPort, [string]$Company, [string]$DnsIdentity, [string]$Server, [string]$ServerInstance, [string]$WebServerInstance, [string]$language, [string]$regionFormat, [string]$TenantId)

Write-Host "Creating Web Site " $WebServerInstance " ..."
New-NAVWebServerInstance -ClientServicesCredentialType $ClientServicesCredentialType -ClientServicesPort $ClientServicesPort -Company $Company -DnsIdentity $DnsIdentity -Server $Server -ServerInstance $ServerInstance -WebServerInstance $WebServerInstance -Language $language -RegionFormat $regionFormat

}
-ArgumentList $ClientServicesCredentialType, $ClientServicesPort, $NewCompanyName, $DnsIdentity, $ServerDNSName, $ServiceInstance, $WebServerInstance, $WebServerLanguage, $WebServerRegionFormat, $TenantId
}
catch [System.Exception] {
Remove-NAVAdminSession -Session $Session
throw $_.Exception
}

finally
{
Remove-NAVAdminSession -Session $Session
}

[/code]

I am using the URL rewrite method in IIS and that has to be enabled. See this post from Microsoft on how to do that.

The next task is to create the required scripts that will install the client and the server updates and update the clickonce distribution.  This will be posted later.

11 thoughts on “Create Instance, Web Client and ClickOnce with PowerShell

  1. laurent says:

    dear Gunnar

    Where is your $ModulePath?

    because I can’t launch this :

    Invoke-Command -Session $Session -ScriptBlock {
    param([string] $ModulePath, [string] $verbosePreference)
    $VerbosePreference = $verbosePreference
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
    Import-Module $ModulePath
    Add-PSSnapin “Microsoft.Dynamics.Nav.Management” -ErrorAction SilentlyContinue
    } -ArgumentList (Join-Path $NAV_AdminRemoteDirectory ‘NAVAdministration.psm1’), $VerbosePreference

    I’m running the clickonce script from the same server as I have NAV directly on Azure.

    Thank you
    Laurent

    1. Hi Laurent

      The invoke-Command function is used to start a process on a remote computer. If you are installing on a local machine you can direcly use the commands that are within the ScriptBlock. In this case the ModulePath is the PowerShell script folder for cloud administration on the NAV DVD.

  2. laurent says:

    thank it works until another error :

    VERBOSE: Getting the ClientUserSettings from remote machine…
    The term ‘Get-NAVClientUserSettings’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
    spelling of the name, or if a path was included, verify that the path is correct and try again.
    + CategoryInfo : ObjectNotFound: (Get-NAVClientUserSettings:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
    + PSComputerName : JX-NAV-PROD-02

    VERBOSE: Done getting the ClientUserSettings from remote machine.
    Edit-NAVClientUserSettings : Cannot bind argument to parameter ‘ClientUserSettings’ because it is null.
    At C:0-Jalix\NavDvd\WindowsPowerShellScripts\Cloud\NAVRemoteAdministration\NAV\New-NAVClientUserSettings.ps1:37 char:56
    + Edit-NAVClientUserSettings -ClientUserSettings $clientUserSettings -Key …
    + ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Edit-NAVClientUserSettings], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Edit-NAVClientUserSettings

    thank you
    Laurent

  3. laurent says:

    I found the error
    in your script you have
    1$clientUserSettings instead $clientUserSettings

    I launch the script and installation works

    but I have error when I launch url clickonce after download clickonce client

    in log I have this error :

    IDENTITIES
    Deployment Identity : ClickOnce_NAV2013R2_APP_at_myurl.cloudapp.net deployment identity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4a6866ef8b76a22d, processorArchitecture=x86

    APPLICATION SUMMARY
    * Installable application.

    ERROR SUMMARY
    Below is a summary of the errors, details of these errors are listed later in the log.
    * Activation of http://myurl.cloudapp.net:9014/Deployment/Microsoft.Dynamics.Nav.Client.application resulted in exception. Following failure messages were detected:
    + Exception reading manifest from http://myurl.cloudapp.net:9014/Deployment/ApplicationFiles/Microsoft.Dynamics.Nav.Client.exe.manifest: the manifest may not be valid or the file could not be opened.
    + The ‘processorArchitecture’ attribute is invalid – The value ‘X86’ is invalid according to its datatype ‘urn:schemas-microsoft-com:asm.v1:processorArchitectureType’ – The Enumeration constraint failed.
    + The Enumeration constraint failed.

    1. Hi Laurent.
      Please let me have a look at the whole script and tell me what it is ment to do.

  4. laurent says:

    Do you want I paste here the whole script?

  5. kpyto32 says:

    Where will be ready scripts that will install the client and the server updates and update the clickonce distribution?

    1. Yes, I have thouse scripts and am hoping for a time to publish them soon.

  6. Francis says:

    Hi ,

    was there a solution for this error ? I am trying to do a deployment and I receive the same error.
    I can’t figure out what’s wrong and checking forums doesn’t show me a solution, it Always brings me back tot this thread.

    Francis

    1. I am planning to have an online session (Lync meeting) where I go through all these scripts and explain them, how they work and how to use them. Would that be of interest to you ?

Leave a Reply

%d bloggers like this: