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.