Test and fix dimensions before upgrading to NAV 2013

A coworker in Advania is working on a Business Intelligence solution called Advania Insight.  He is working on the installation for a company that is running NAV.  This company has data from Navigator since 1994.

One of the problem we saw was that some of the dimension codes used where not a standard dimension but for example a heading or a footer.  This did not go to well through the analysis and we needed to fix this.

Manually this is a lot of work so I did some SQL queries to find these errors. For example, this query will find all entries in the G/L Entry table that has incorrect value Global Dimension 1 Code.

[code lang=”sql”]USE [Demo Database NAV (7-1)]
GO

SELECT
[Entry No_]
FROM [dbo].[CRONUS Ísland hf_$G_L Entry],
[dbo].[CRONUS Ísland hf_$Dimension Value]
WHERE [Global Dimension 1 Code] = [Code] AND
[Global Dimension No_] = 1 AND
[Dimension Value Type] > 0
GO
[/code]

This query can be used to find entries with missing entries in the Ledger Entry Dimension table.

[code lang=”sql”]SELECT [Entry No_]
FROM [dbo].[CRONUS Ísland hf_$G_L Entry] V
WHERE (SELECT COUNT(D.[Dimension Code]) FROM [dbo].[CRONUS Ísland hf_$Ledger Entry Dimension] D
WHERE [Table ID] = 17 AND V.[Entry No_] = D.[Entry No_] AND D.[Dimension Code] = ‘DEILD’) = 0
AND [Global Dimension 1 Code] <> ”
GO
[/code]

This query can be used to find entries with Global Dimension 1 Code that are missing from the Dimension Value table.

[code lang=”sql”]USE [Demo Database NAV (7-1)]
GO

SELECT [Entry No_]
FROM [dbo].[CRONUS Ísland hf_$G_L Entry] V
WHERE (SELECT COUNT(D.[Dimension Code]) FROM [dbo].[CRONUS Ísland hf_$Dimension Value] D
WHERE [Global Dimension 1 Code] = D.[Code] AND D.[Global Dimension No_] = 1) = 0
AND [Global Dimension 1 Code] <> ”
GO[/code]

Looking forward I saw that it would be a lot of work to manually check all these possibilities so I decided to write a SQL script. First a script that will do the dimension type test on the Ledger Entry Dimension table.

[code lang=”sql”]USE [KS Dynamics NAV]
GO

DECLARE @invalididentifierscars varchar(10)
DECLARE @replacestring varchar(10)
SET @invalididentifierscars = (SELECT [invalididentifierchars] FROM [dbo].[$ndo$dbproperty])

DECLARE @varSQL varchar(max)
DECLARE @DimTableName varchar(256)
DECLARE @DimValueTableName varchar(256)
DECLARE @getCompanyNames CURSOR
DECLARE @CompanyName varchar(50)
DECLARE @loop int
CREATE TABLE #TmpLedgerEntryDimCombinations (CompanyName varchar(50), TableID int, TableName varchar(50), GlobalDimCode varchar(20), GlobalDimNo int)
CREATE TABLE #TmpIncorrectDimEntries (CompanyName varchar(50), TableID int, TableName varchar(50), GlobalDimCode varchar(20), GlobalDimNo int, EntryNo int)
SET @getCompanyNames = CURSOR FOR SELECT [Name] FROM [dbo].[Company]
OPEN @getCompanyNames
FETCH NEXT FROM @getCompanyNames INTO @CompanyName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @DimTableName = @CompanyName + ‘$Ledger Entry Dimension’
SET @DimValueTableName = @CompanyName + ‘$Dimension Value’
SET @loop = 0
WHILE @loop < LEN(@invalididentifierscars)
BEGIN
SET @loop = @loop + 1
SET @DimTableName = REPLACE(@DimTableName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
SET @DimValueTableName = REPLACE(@DimValueTableName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
END
SET @varSQL = ‘USE [‘ + DB_NAME() + ‘]; INSERT INTO #TmpLedgerEntryDimCombinations SELECT DISTINCT ”’ + @CompanyName + ”’,[Table ID],O.[Name],L.[Dimension Code],[Global Dimension No_]
FROM [dbo].[‘ + @DimTableName + ‘] L,[dbo].[‘ + @DimValueTableName + ‘] D, [dbo].[Object] O
WHERE D.[Dimension Code] = L.[Dimension Code] AND [Global Dimension No_] > 0 AND O.[Type] = 0 AND O.[ID] = [Table ID]’
EXEC (@varSQL)
FETCH NEXT FROM @getCompanyNames INTO @CompanyName
END
CLOSE @getCompanyNames
DEALLOCATE @getCompanyNames

DECLARE @TableID int
DECLARE @TableName varchar(50)
DECLARE @GlobalDimCode varchar(20)
DECLARE @GlobalDimNo int
DECLARE @DimFieldName varchar(50)
DECLARE @DimFixes CURSOR
SET @DimFixes = CURSOR FOR SELECT CompanyName, TableID, TableName, GlobalDimCode, GlobalDimNo FROM #TmpLedgerEntryDimCombinations
OPEN @DimFixes
FETCH NEXT FROM @DimFixes INTO @CompanyName, @TableID, @TableName, @GlobalDimCode, @GlobalDimNo
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @OrigCompanyName varchar(50)
DECLARE @OrigTableName varchar(50)
SET @OrigCompanyName = @CompanyName
SET @OrigTableName = @TableName
SET @loop = 0
WHILE @loop < LEN(@invalididentifierscars)
BEGIN
SET @loop = @loop + 1
SET @CompanyName = REPLACE(@CompanyName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
SET @TableName = REPLACE(@TableName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
END
SET @DimFieldName = ‘[Global Dimension ‘ + CAST(@GlobalDimNo as varchar(1)) + ‘ Code]’
SET @varSQL = ‘INSERT INTO #TmpIncorrectDimEntries (CompanyName, TableID, TableName, GlobalDimCode, GlobalDimNo, EntryNo)
SELECT ”’ + @OrigCompanyName + ”’, ‘ + CAST(@TableID as varchar(20)) + ‘,”’ + @OrigTableName + ”’,”’ + @GlobalDimCode + ”’,’ + CAST(@GlobalDimNo as varchar(1)) + ‘, [Entry No_]
FROM [dbo].[‘ + @CompanyName + ‘$’ + @TableName + ‘], [dbo].[‘ + @CompanyName + ‘$Dimension Value]
WHERE ‘ + @DimFieldName + ‘ = [Code] AND [Global Dimension No_] = ‘ + CAST(@GlobalDimNo as varchar(1)) + ‘ AND [Dimension Value Type] > 0’
EXEC(@varSQL)
FETCH NEXT FROM @DimFixes INTO @CompanyName, @TableID, @TableName, @GlobalDimCode, @GlobalDimNo
END
CLOSE @DimFixes;
DEALLOCATE @DimFixes;

SELECT * FROM #TmpLedgerEntryDimCombinations
DROP TABLE #TmpLedgerEntryDimCombinations
SELECT * FROM #TmpIncorrectDimEntries
DROP TABLE #TmpIncorrectDimEntries
GO

[/code]

Then a similar script that will update the Ledger Entry Dimension table according to the values in Global Dimension 1 Code and Global Dimension 2 Code.

[code lang=”sql”]USE [KS Dynamics NAV]
GO

DECLARE @invalididentifierscars varchar(10)
DECLARE @replacestring varchar(10)
SET @invalididentifierscars = (SELECT [invalididentifierchars] FROM [dbo].[$ndo$dbproperty])

DECLARE @varSQL varchar(max)
DECLARE @DimTableName varchar(256)
DECLARE @DimValueTableName varchar(256)
DECLARE @getCompanyNames CURSOR
DECLARE @CompanyName varchar(50)
DECLARE @loop int
CREATE TABLE #TmpLedgerEntryDimCombinations (CompanyName varchar(50), TableID int, TableName varchar(50), GlobalDimCode varchar(20), GlobalDimNo int)
SET @getCompanyNames = CURSOR FOR SELECT [Name] FROM [dbo].[Company]
OPEN @getCompanyNames
FETCH NEXT FROM @getCompanyNames INTO @CompanyName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @DimTableName = @CompanyName + ‘$Ledger Entry Dimension’
SET @DimValueTableName = @CompanyName + ‘$Dimension Value’
SET @loop = 0
WHILE @loop < LEN(@invalididentifierscars)
BEGIN
SET @loop = @loop + 1
SET @DimTableName = REPLACE(@DimTableName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
SET @DimValueTableName = REPLACE(@DimValueTableName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
END
SET @varSQL = ‘USE [‘ + DB_NAME() + ‘]; INSERT INTO #TmpLedgerEntryDimCombinations SELECT DISTINCT ”’ + @CompanyName + ”’,[Table ID],O.[Name],L.[Dimension Code],[Global Dimension No_]
FROM [dbo].[‘ + @DimTableName + ‘] L,[dbo].[‘ + @DimValueTableName + ‘] D, [dbo].[Object] O
WHERE D.[Dimension Code] = L.[Dimension Code] AND [Global Dimension No_] > 0 AND O.[Type] = 0 AND O.[ID] = [Table ID]’
EXEC (@varSQL)
FETCH NEXT FROM @getCompanyNames INTO @CompanyName
END
CLOSE @getCompanyNames
DEALLOCATE @getCompanyNames

DECLARE @TableID int
DECLARE @TableName varchar(50)
DECLARE @GlobalDimCode varchar(20)
DECLARE @GlobalDimNo int
DECLARE @DimFieldName varchar(50)
DECLARE @DimFixes CURSOR
SET @DimFixes = CURSOR FOR SELECT CompanyName, TableID, TableName, GlobalDimCode, GlobalDimNo FROM #TmpLedgerEntryDimCombinations
OPEN @DimFixes
FETCH NEXT FROM @DimFixes INTO @CompanyName, @TableID, @TableName, @GlobalDimCode, @GlobalDimNo
WHILE @@FETCH_STATUS = 0
BEGIN
SET @loop = 0
WHILE @loop < LEN(@invalididentifierscars)
BEGIN
SET @loop = @loop + 1
SET @CompanyName = REPLACE(@CompanyName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
SET @TableName = REPLACE(@TableName,SUBSTRING (@invalididentifierscars,@loop,1),’_’)
END
SET @DimFieldName = ‘[Global Dimension ‘ + CAST(@GlobalDimNo as varchar(1)) + ‘ Code]’
SET @varSQL = ‘DELETE FROM [‘ + @CompanyName + ‘$Ledger Entry Dimension]
WHERE [Table ID] = ‘ + CAST(@TableID as varchar(20)) + ‘ AND [Dimension Code] = ”’ + @GlobalDimCode + ””
EXEC(@varSQL)
SET @varSQL = ‘INSERT INTO [‘ + @CompanyName + ‘$Ledger Entry Dimension] ([Table ID],[Entry No_],[Dimension Code],[Dimension Value Code])
SELECT ‘ + CAST(@TableID as varchar(20)) + ‘,[Entry No_],”’ + @GlobalDimCode + ”’,’ + @DimFieldName + ‘
FROM [dbo].[‘ + @CompanyName + ‘$’ + @TableName + ‘] E
WHERE ‘ + @DimFieldName + ‘ <> ””’
EXEC(@varSQL)
FETCH NEXT FROM @DimFixes INTO @CompanyName, @TableID, @TableName, @GlobalDimCode, @GlobalDimNo
END
CLOSE @DimFixes;
DEALLOCATE @DimFixes;

DROP TABLE #TmpLedgerEntryDimCombinations
GO

[/code]

Reading through this you should see that the basic part of the two scripts are similar and could be used to build further testing and for other dimension tables.  It is important that all the testing should be done before upgrading to NAV 2013 or NAv 2013 R2.

There are a lot of tables you will need to consider and do a data check for.

DimensionTables

Some of you have most likely written some kind of dimension test.  Perhaps you can also share them here ?

NAV Service Startup Type in PowerShell scripting

I have spent some time creating PowerShell scripts to solve all my installation and upgrade tasks.  I am now able to install Dynamics NAV on both local and remote computers.  I can install databases, services, clickonce distribution and web clients.  And my final task last week was to be able to install knowledge base package in a few minutes that upgrades services, client, webclient and clickonce.  Likely to create a blog about that later.

As a part of this process I have created several PowerShell scripts and functions.  I will find a time to share this with you over the next weeks.

For now I would like to share one function.

When you install a NAV Service Instance from the DVD the service startup is set to “Automatic (Delayed Start)”.

NAVServiceStartupType

There is a reason for this.  I have seen servers unable to finish the startup process if the service startup type is set to “Automatic” only.  There is a period of time needed before the service can start after the computer startup.

The problem I am facing with my scripts is that the command New-NAVServerInstance will leave the service in “Automatic” startup mode.  I wanted to change this so I created a function.

[code lang=”powershell”]# Set Service Startup Mode to Automatic (delayed)
function Set-ServiceStartupModeRemotely
{
[CmdletBinding()]
param (
[parameter(Mandatory=$true)]
[string] $ServiceInstance,

[parameter(Mandatory=$true)]
[System.Management.Automation.Runspaces.PSSession] $Session
)
PROCESS {

Invoke-Command -Session $Session -ScriptBlock {
param([string] $ServiceInstance, [string] $verbosePreference)
$VerbosePreference = $verbosePreference
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
$Service = ‘MicrosoftDynamicsNavServer$’ + $ServiceInstance
$Computer = ‘LOCALHOST’
$command = ‘sc.exe \\$Computer config "$Service" start= delayed-auto’
$Output = Invoke-Expression -Command $Command -ErrorAction Stop
if($LASTEXITCODE -ne 0){
Write-Host "$Computer : Failed to set $Service to delayed start.
More details: $Output" -foregroundcolor red
} else {
Write-Host "$Computer : Successfully changed $Service service
to delayed start" -foregroundcolor green
}

} -ArgumentList
$ServiceInstance,

$VerbosePreference

return $ManifestList
}
}
Export-ModuleMember -Function Set-ServiceStartupModeRemotely[/code]

I added this function to my New-Instance script and call the function after the service has started.

WARNING: Waiting for service ‘Microsoft Dynamics NAV Server [NAV71_KAPPI] (MicrosoftDynamicsNavServer$NAV71_KAPPI)’ to start…

LOCALHOST : Successfully changed MicrosoftDynamicsNavServer$NAV71_KAPPI service to delayed start

Setup your own server – license available

I was going through my shelfs and found a Windows Web Server 2008 R2 .  Since I moved my web sites to Azure I am not using this license any more.

microsoft_windows_web_server_2008_r2_en

Do you want this DVD and License ?  Or just the License ?

You can install NAV Server on this version.  Everything will work except the NAV Help Sever.  NAV Help Server requires Windows Search Services that are part of the File Sharing Role and that role is not included in Windows Web Server 2008 R2.

Send me a line.

Using XML to transfer Data between NAV companies

In November 2010 I blogged about Transferring small amount of data between databases.  There I had a Form that can read and write a XML file with table data.  This Form was used frequently in my company to move data between companies and databases.

The good thing about this method is that I can move data between database versions.

XMLDataTransfer

Yesterday I got a request to share a NAV 2013 R2 version of this solution.  Here it is !

This version includes support for BLOB data in the tables.  BLOB data is converted to Base64 and included in the XML file.

Be careful when importing.  Existing data will be overwritten.

Dynamics XML Data Transfer for NAV 2013 R2

ClosedXML gives me a SmartTag error in NAV 2013 R2

In NAV 2009 R2 I did a solution that used ClosedXML to create Excel Documents.  ClosedXML is built to give an easy access to the office documents and is built on top of the Open XML SDK 2.0 for Microsoft Office.

I upgraded this solution to NAV 2013 without any problems.  Then to NAV 2013 R2 and there is where the problem started.

With NAV 2013 Microsoft changed the way NAV creates Excel documents.  They move away from the old COM model to this Open XML method.  You can see the changes in Table 370 Excel Buffer.

In NAV 2013 Microsoft used version 2.0 of the Open XML SDK.  In NAV 2013 R2 Microsoft is using version 2.5 and that is where my problems started.

When trying to save the Excel document with the SaveAs function I got a SmartTag error.

Could not load type ‘DocumentFormat.OpenXml.Spreadsheet.SmartTags’

I searched and found that the SmartTags are discontinued in version 2.5 but they are still used in ClosedXML.  As usual there is a solution and I found it on the Closed XML forum.

A WORKAROUND TO GET CLOSEDXML working for DocumentFormat.OpenXml SDK 2.5:

  1. Download ClosedXML source code
  2. Open ClosedXML source with Visual Studio
  3. Browse to the References of “ClosedXML” project and remove the reference DocumentFormat.OpenXml
  4. Use Nuget to install the latest Document.OpenXml 2.5 SDK as reference into the “ClosedXML” project
  5. Open the file XLWSContentManager.cs and remove the line 86
  6. Compile the project

For some reason it does not work for that way for “ClosedXML_Net3.5” project.

I opened my NAV 2013 R2 Developement machine with Visual Studio 2012, opened the solution and removed the ClosedXML_Net3.5 project.  Then replaced all references to Document.OpenXML with the pre-installed version 2.5 – yes the version 2.5 is installed with NAV Server.

The new ClosedXML 0.69.1.0 was built and it works with OpenXML 2.5 and I can now do the upgrade from NAV 2013 to NAV 2013 R2.

Here is the ClosedXML version 0.69.1.0 rebuilt for Document.OpenXML 2.5

Strange behaviour in Outlook 2013

I just upgraded my home network to Windows Server 2012 R2, Exchange 2013 and Office 2013.  I use the Essentials Role on my primary domain controller to handle the home computers.

I have a few email addresses and I wanted to pull them into my Exchange folder via POP interface.  So – I added two POP accounts.  Then the strangest thing happened, the mail I got through the POP accounts all appeared as Draft and ready to be sent.

GmailUnSentMail

With the email coming in this way there is no way to forward or reply to the message.

I looked for the solution and found posts like this: Emails as draft on inbox web apps and locally but they did not help.

The solution is simple but the functionality is questionable to say the least.  Just make sure that your exchange account is using cached mode.

CachedModeEnabled

Add-ins Downloaded and Installed with NAV C/AL

I was watching Vjeko’s lecture in NAVTechDays 2013.  He gave me an idea on the add-in installation.  Thank you Vjeko.

It is now finished and time to share this with you all.  The basic function Codeunit is used to handle the downloading and installing of the add-in.  In the Add-on Setup Page I put an action to trigger the Add-ins validation.

CheckAddIns

This function will call the CheckAddins trigger on the Setup Table.  There I have a simple code

[code]
IF NOT CANLOADTYPE(HardwareHubProxy) THEN
AddinMgt.DownloadAnInstallServerAddIn(
‘HardwareHubProxy.dll’,
‘https://dl.dropboxusercontent.com/u/33900811/NAV/Add-Ins/Server/Advania/HardwareHubProxy.dll’,
Text002);[/code]

That will check if the add-in is installed and if not call a trigger in the add-ins function Codeunit to install the add-in.  This works both for the client and server.

In this example I am installing the HardwareHubProxy.dll that is required on the server to use the Hardware Hub.  Text002 is ‘ENU=Connection to the Advania Hardware Hub;ISL=Tenging við vélbúnaðargátt Advania’.

When installing the add-in I begin by downloading it into a Base64 string.  This is always done on the server.  The add-in is then saved to a temporary file, on the server for a server add-in and on the client for a client add-in.

Next I create a command file that is used to copy the temporary file to the add-ins folder.  This command file is then executed.  Before execution there is a check to see if the current user is able to write to the add-in directly.  If so, then the add-in is copied to the add-ins folder and the process finishes.

If the service user is not able to write to the server add-ins folder the user will get a save-as dialog and is asked to copy the file to the server add-ins folder.  In all my tests the server has been able to save the add-in without problems.

By default the running client will not be able to write the add-in to the client add-ins folder.  If this is the case then a confirmation dialog will be prompted as the copy function is running in an elevated access mode.

AddInFunctions

Create Instance, Web Client and ClickOnce with PowerShell

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.