<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Freddys Blog</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/freddyk/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/freddyk/feed.xml</link>
    <description />
    <lastBuildDate>Fri, 08 Mar 2019 10:04:48 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Moving day...</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/moving-day</link>
      <pubDate>Fri, 08 Mar 2019 10:04:48 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=5125</guid>
      <description><![CDATA[The MSDN blog platform is under retirement and I have decided to move my blog to be mastered in...]]></description>
      <content:encoded><![CDATA[The MSDN blog platform is under retirement and I have decided to move my blog to be mastered in <a href="https://freddysblog.com">https://freddysblog.com</a>

The blog will also be syndicated to <a href="https://community.dynamics.com/business/b/freddysblog">https://community.dynamics.com/business/b/freddysblog</a>

&nbsp;

Meaning, that this is the final blog post on my MSDN blog.

&nbsp;

Over and out

&nbsp;

<span style="color: #0000ff"><em><strong>Freddy Kristiansen</strong></em></span>
Technical Evangelist]]></content:encoded>
    </item>
    <item>
      <title>What's new in NavContainerHelper 0.5.0.1</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/whats-new-in-navcontainerhelper-0-5-0-1</link>
      <pubDate>Thu, 31 Jan 2019 14:05:54 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=5055</guid>
      <description><![CDATA[Run As Administrator - or not!
NavContainerHelper now supports running as a standard user, but it...]]></description>
      <content:encoded><![CDATA[<h1>Run As Administrator - or not!</h1>
NavContainerHelper now supports running as a standard user, but it does require you to add some permissions to this user:
<ul>
 	<li>Full Control to the folder C:\ProgramData\NavContainerHelper and all subfolders and files.</li>
 	<li>Modify permissions to the file C:\Windows\System32\drivers\etc (if you want to use the -updateHosts switch on New-NavContainer).</li>
 	<li>Full Control to the docker engine pipe as described in Tobias Fensters blog post <a href="https://www.axians-infoma.com/techblog/allow-access-to-the-docker-engine-without-admin-rights-on-windows/">here</a>.</li>
</ul>
After installing or updating the NavContainerHelper, you can run the command Check-NavContainerHelperPermissions
<h1>Check-NavContainerHelperPermissions</h1>
During initialization of NavContainerHelper, it automatically runs Check-NavContainerHelperPermissions with the -silent parameter. This will check the permissions for the user and display any missing permissions. Note that if you are running as administrator, you do have the right permissions.

You can also run Check-NavContainerHelperPermissions yourself and the output could look like this:

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/check-permissions.png"><img src="https://msdnshared.blob.core.windows.net/media/2019/01/check-permissions.png" alt="" class="alignnone size-full wp-image-5065" width="832" height="142" /></a>

You can ignore the hosts file permission check by adding -<strong>IgnoreHosts</strong>.

Which also indicates that I can run
<pre>Check-NavContainerHelperPermissions -Fix</pre>
or
<pre>Check-NavContainerHelperPermissions -Fix -IgnoreHosts</pre>
to fix the permissions for me:

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/fix-permissions.png"><img src="https://msdnshared.blob.core.windows.net/media/2019/01/fix-permissions.png" alt="" class="alignnone size-full wp-image-5075" width="750" height="194" /></a>

Note that PowerShell might ask for permissions to run some scripts elevated in order to assign these permissions.

After that, permissions are OK:

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/permissions-ok.png"><img src="https://msdnshared.blob.core.windows.net/media/2019/01/permissions-ok.png" alt="" class="alignnone size-full wp-image-5085" width="751" height="148" /></a>

The script for checking and fixing permissions can be found here: <a target="_blank" href="https://github.com/Microsoft/navcontainerhelper/blob/master/Check-NavContainerHelperPermissions.ps1" rel="noopener">https://github.com/Microsoft/navcontainerhelper/blob/master/Check-NavContainerHelperPermissions.ps1</a>
<h1>Manually assigning permissions</h1>
If for some reason the script doesn't work on your machine, you can also assign the permissions manually.
<h2>Full Control to the folder C:\ProgramData\NavContainerHelper and all subfolders and files.</h2>
Navigate to C:\ProgramData and open properties for the NavContainerHelper folder and add permissions for the user, it should look like this:

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/folderpermissions.png"><img src="https://msdnshared.blob.core.windows.net/media/2019/01/folderpermissions.png" alt="" class="alignnone size-full wp-image-5095" width="767" height="520" /></a>
<h2>Modify permissions to the file C:\Windows\System32\drivers\etc</h2>
Only if you are planning to use the -updateHosts flag on New-NavContainer (where the hosts file is automatically updated with the IP number of the container), you need this permission. It should look like this:

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/hostspermissions.png"><img src="https://msdnshared.blob.core.windows.net/media/2019/01/hostspermissions.png" alt="" class="alignnone size-full wp-image-5105" width="767" height="520" /></a>
<h2>Full Control to the docker engine pipe</h2>
Tobias Fenster has written a blog post and created a small PowerShell module, which fixes this issue. Please read Tobias Fensters blog post <a href="https://www.axians-infoma.com/techblog/allow-access-to-the-docker-engine-without-admin-rights-on-windows/">here</a>.
<h1>Invoke-ScriptInNavContainer</h1>
In the new NavContainerHelper you will find a function called Invoke-ScriptInNavContainer and it does exactly like the name says - it invokes a script in a NavContainer.
<pre>Invoke-ScriptInNavContainer -containerName test -scriptblock { Get-NavServerUser NAV }</pre>
If you are running as an administrator, NavContainerHelper will create a PsSession to the container and invoke the script in a session.

If you are running as an end user, NavContainerHelper will save the script in a shared folder and use docker exec to run the script in the container. The return value from the script will also be serialized into a file in the shared folder and deserialized in the script.

<strong>Note:</strong> You can transfer Secure strings to the script (they will be transferred using a random encryption key), but currently Secure strings returned from the script is not supported/cannot be unpacked. Let me know if this is needed and I will consider.

<strong>The biggest difference is really that outputting this to Host</strong> in your script will go to the Host if you are running as administrator and it will be returned as a return value if running as an end user.

Example:
<pre>$a = Invoke-ScriptInNavContainer -containerName test -scriptblock { Write-Host "This is a test" }</pre>
When run as administrator, <strong>this will output the string "This is a test" to the Host and $a will be empty</strong>. When you run this as an end user, <strong>nothing will output to the Host and $a will contain "This is a test"</strong>.

My recommendation is that you<strong> avoid outputting to Host in the Scriptblock</strong>.

<span style="text-decoration: underline"><em><strong>All places within NavContainerHelper, where a script is invoked in a NavContainer has been replaced by a call to Invoke-ScriptInNavContainer.</strong></em></span>
<h1>Using the best container image</h1>
After releasing ltsc2019 images and NavContainerHelper 0.4.3.2, I could see that a number of people still would spin up 2016 images, simply because they were already downloaded. The policy was, that if you reference an image (eg. microsoft/dynamics-nav) without specifying a tag and without specifying -alwayspull - and you had that image downloaded, then NavContainerHelper would reuse that.

This is still going to happen - but now you will see a note when starting the container, which indicates that you aren't using the optimal image:

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/usebestsuggestion.png"><img src="https://msdnshared.blob.core.windows.net/media/2019/01/usebestsuggestion-1024x210.png" alt="" class="alignnone size-large wp-image-5115" width="1024" height="210" /></a>

adding -useBestContainerOS will force NavContainerHelper to use the optimal image and even re-platform the image if needed.
<h1>New function Get-NavContainerImageTags</h1>
Try to run this script:
<pre>$imageName = "mcr.microsoft.com/businesscentral/onprem"
$tags = (Get-NavContainerImageTags -imageName $imageName).Tags | Where-Object { $_.contains("w1-ltsc2019") }</pre>
$tags will now contain all tags of ltsc2019 images with w1. While writing this blog post, that is:
<pre>13.0.24630.0-w1-ltsc2019
13.1.25940.0-w1-ltsc2019
13.2.26556.0-w1-ltsc2019
13.3.27233.0-w1-ltsc2019
cu1-w1-ltsc2019
cu2-w1-ltsc2019
cu3-w1-ltsc2019
latest-w1-ltsc2019
rtm-w1-ltsc2019
w1-ltsc2019</pre>
Grabbing all tags will right now return 572 image tags, which is 3 platforms (default (which is the same as ltsc2016), ltsc2016 and ltsc2019), 4 CU's, 2 CU tags (version number and CU) - and all of that times 20 localizations. On top of that there are a number of latest tags.
<h1>New function Get-NavContainerImageLabels</h1>
I have often found that I would like to know whether there is a new version of a specific image without pulling it.

Get-NavContainerImageLabels will download the labels section of the image manifests without pulling the image.
<pre>Get-NavContainerImageLabels -imageName "mcr.microsoft.com/businesscentral/onprem:cu4"</pre>
will return nothing until cu4 ships.
<pre>Get-NavContainerImageLabels -imageName "mcr.microsoft.com/businesscentral/onprem:cu3</pre>
will (today) return
<pre>country : W1
created : 201901171250
cu : 
eula : https://go.microsoft.com/fwlink/?linkid=861843
legal : http://go.microsoft.com/fwlink/?LinkId=837447
maintainer : Dynamics SMB
nav : 
osversion : 10.0.14393.2665
platform : 13.0.27183.0
tag : 0.0.9.0
version : 13.3.27233.0</pre>
Indicating that the cu field in the labels isn't set, which is an error that will be fixed with the next rebuild round. When the images are being rebuild, the created date will also change.

Also if you want to know the labels of the latest Business Central On Prem image:
<pre>Get-NavContainerImageLabels -imageName "mcr.microsoft.com/businesscentral/onprem:latest-ltsc2019"</pre>
Finally, if you want to have all Business Central tags, sorted by image creation date:
<pre>$imageName = "mcr.microsoft.com/businesscentral/onprem"
$tags = (Get-NavContainerImageTags -imageName $imageName).Tags | Where-Object { $_.contains("w1-ltsc2019") }
($tags | % { Get-NavContainerImageLabels -imageName "${imageName}:$_" } | Sort-Object -Descending -Property created).version | Select -Unique</pre>
Which won't return the images ordered by version number...

If you want to see all versions of NAV 2018, ordered by version number, you need to
<pre>$imageName = "microsoft/dynamics-nav"
$tags = (Get-NavContainerImageTags -imageName $imageName).Tags | Where-Object { $_.startswith("11.0") } | Where-Object { $_.contains("w1-ltsc2019") }
$labels = $tags | % { Get-NavContainerImageLabels -imageName "${imageName}:$_" }
($labels | Sort-Object -Descending -Property version).version</pre>
You should see:
<pre>11.0.26893.0
11.0.26401.0
11.0.25466.0
11.0.24742.0
11.0.24232.0
11.0.23572.0
11.0.23019.0
11.0.22292.0
11.0.21836.0
11.0.21441.0
11.0.20783.0
11.0.20348.0
11.0.19846.0
11.0.19394.0</pre>
&nbsp;

Enjoy

<span style="color: #0000ff"><em><strong>Freddy Kristiansen</strong></em></span>
<span>Technical Evangelist</span>]]></content:encoded>
    </item>
    <item>
      <title>Process Isolation for containers in Windows 10</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/process-isolation-for-containers-in-windows-10</link>
      <pubDate>Sun, 13 Jan 2019 08:02:56 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4995</guid>
      <description><![CDATA[For a few months we have known that Docker for Windows would get support for process isolation under...]]></description>
      <content:encoded><![CDATA[For a few months we have known that Docker for Windows would get support for process isolation under Windows 10. Arend-Jan Kauffmann explained how to use nightly builds from Docker to get the feature early and test it. I of course jumped on this and have been running a nightly build of Docker since December 6th.

A few days ago the feature was released in the edge release of Docker Desktop.
<h1>No updates?</h1>
I tried to check for updates, but this didn't reveal any new release. I replaced the docker executables with the original files and tried again - still no updates.

I checked the edge release notes: <a target="_blank" href="https://docs.docker.com/docker-for-windows/edge-release-notes/" rel="noopener">https://docs.docker.com/docker-for-windows/edge-release-notes/</a> - no updates.

I did some investigation and found that this was probably caused by Docker for Windows being renamed to Docker Desktop and the version numbering schema has changed.
<h1>This worked for me!</h1>
I decided to uninstall docker and install the edge release from here: <a target="_blank" href="https://hub.docker.com/editions/community/docker-ce-desktop-windows" rel="noopener">https://hub.docker.com/editions/community/docker-ce-desktop-windows</a>

Unfortunately this also didn't go as smooth as expected. I ran into this issue: <a target="_blank" href="https://success.docker.com/article/dockerforwin-install-fails-on-installationmanifestjson" rel="noopener">https://success.docker.com/article/dockerforwin-install-fails-on-installationmanifestjson</a> - but fortunately, I could resolve this by following the resolution in this blog post.

After installing, the About Docker dialog shows that I am running engine 18.09.1, which is the first version supporting the process isolation.

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/18.09.11.png"><img width="586" height="488" class="alignnone size-full wp-image-5035" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/01/18.09.11.png" /></a>

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/18.09.1.png"></a>
<h1>NavContainerHelper support (as of today)</h1>
NavContainerHelper 0.4.3.0 or newer will default the isolation mode to process when running Windows 10 1809 and Docker 18.09.1 (or a daily build with support for process isolation) and will display this in the output

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/output.png"><img width="874" height="354" class="alignnone size-full wp-image-5005" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/01/output.png" /></a>
<h1>Proof</h1>
Inspecting the processes on the host and in the container reveals that you indeed are running process isolation:

<a href="https://msdnshared.blob.core.windows.net/media/2019/01/process.png"><img width="1024" height="253" class="alignnone size-large wp-image-5015" alt="" src="https://msdnshared.blob.core.windows.net/media/2019/01/process-1024x253.png" /></a>
<h1>Recommendation</h1>
If you are running Windows 10, I recommend you to update to 1809 and update Docker to the 18.09.1 release.

Running NAV/BC containers in process isolation is a <strong>HUGE</strong> win over hyperv isolation.

&nbsp;
<p style="margin: 0px 0px 10px;color: #333333;font-family: 'Segoe UI', Tahoma, Arial, 'Helvetica Neue', Helvetica, sans-serif;font-size: 14px;font-style: normal;font-weight: 400;letter-spacing: normal;text-align: start;text-indent: 0px;background-color: #ffffff">Enjoy</p>
<p style="margin: 0px 0px 10px;color: #333333;font-family: 'Segoe UI', Tahoma, Arial, 'Helvetica Neue', Helvetica, sans-serif;font-size: 14px;font-style: normal;font-weight: 400;letter-spacing: normal;text-align: start;text-indent: 0px;background-color: #ffffff"><span style="color: #0000ff"><em><strong>Freddy Kristiansen</strong></em></span>
<span>Technical Evangelist</span></p>]]></content:encoded>
    </item>
    <item>
      <title>Clean up after yourself Docker, your mom isn't here!</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/clean-up-after-yourself-docker-your-mom-isnt-here</link>
      <pubDate>Tue, 11 Dec 2018 15:04:26 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4965</guid>
      <description><![CDATA[Docker is an amazing tool.

As Dynamics 365 Business Central or Microsoft Dynamics NAV developers...]]></description>
      <content:encoded><![CDATA[Docker is an amazing tool.

As Dynamics 365 Business Central or Microsoft Dynamics NAV developers we can spin up any version of the platform and app in minutes and use our favourite tools to work with it.

Using the layering techniques from Docker, these images will be stored on disc as efficient as possible and every time I start up a new container, it reuses the disc space and your running container just becomes another layer on top of your image.

Really really smart, but it does take up some disc space and it isn't very good at cleaning up.
<h1>Folders</h1>
Docker will store all images and containers in a folder called <strong>C:\ProgramData\Docker</strong>. You can modify the location of this folder when configuring Docker by changing a setting called <strong>data-root</strong>. Read more here: <a target="_blank" href="https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-docker/configure-docker-daemon" rel="noopener">https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-docker/configure-docker-daemon</a>

NavContainerHelper will store extracted image files and object sources under <strong>C:\ProgramData\NavContainerHelper</strong>.

I typically run Docker on Azure VMs, which doesn't live long. When I need a new version, I use <a target="_blank" href="http://aka.ms/getbc" rel="noopener">http://aka.ms/getbc</a> to spin up a new VM. I never really had any issues with these folders taking up space. I have however gotten the question from a number of people running Docker locally: <strong>How do I clean up after Docker?</strong> and after Windows 10 1809 came out and after Windows 10 supports process isolation (if you use Docker insider builds) I have started using my laptops more and more and I decided that I would investigate how this is done.
<h1>The process is simple</h1>
What we need to do is simple:
<ol>
 	<li>Remove all running Containers</li>
 	<li>Remove all images, build caches and other things Docker related.</li>
 	<li>Empty the NavContainerHelper folder</li>
 	<li>Pull the images you need</li>
</ol>
It is the how-to, which becomes complicated - especially #2. You might have tried to delete the folder manually or tried to delete individual layers, just to find that you get a ton of errors and if you finally succeed deleting things, you might have ruined your host operating system and only a reinstall will get you back up running.

The reason for this is that the folders contains a lot of symbolic links (when they say that they are sharing the OS with the host, they mean it) and you need to be a backup/restore service in order to touch/delete these files. No user can do so, not even the administrator.

So who can?
<h1>Docker-Ci-Zap can!</h1>
<a target="_blank" href="https://github.com/jhowardmsft/docker-ci-zap" rel="noopener">Docker-Ci-Zap</a> is an open source project, written in a programming language called go. Read more about go <a target="_blank" href="https://golang.org/" rel="noopener">here</a>!

It uses the <a target="_blank" href="https://github.com/microsoft/hcsshim" rel="noopener">Host Compute Service Shim</a>, another go project, used of the Docker Host foundation.

It basically enumerates the layes and destroys them.

<strong>Disclaimer: There is absolutely NO guarantee that this tool works and it might cause your computer to need a reinstall.</strong>

But it worked for me.

So, I have built a script around this tool. A script, which performs the other 3 steps and as step 2, automatically downloads Docker-Ci-Zap and calls it with your Docker Data Dir. The script will detect that Docker is installed and determine that you are running Windows Containers and detect your Docker Root Folder.

If you successfully run the script, you should see something like:

<a href="https://msdnshared.blob.core.windows.net/media/2018/12/remove2.png"><img src="https://msdnshared.blob.core.windows.net/media/2018/12/remove2.png" alt="" width="793" height="224" class="alignnone size-full wp-image-4975" /></a>

And both your Docker folder and your NavContainerHelper folder should now be empty.

If you encounter an error like:

<a href="https://msdnshared.blob.core.windows.net/media/2018/12/remove1.png"><img src="https://msdnshared.blob.core.windows.net/media/2018/12/remove1.png" alt="" width="894" height="293" class="alignnone size-full wp-image-4985" /></a>

It is likely because you need a restart of your computer and a re-run of the script.
<h1><span style="color: #ff0000">NOTE: This script will delete all Containers and Images</span></h1>
The Script can be copied from below or downloaded from this URL: <a target="_blank" href="https://raw.githubusercontent.com/Microsoft/nav-arm-templates/master/CleanupAfterDocker.ps1" rel="noopener">https://raw.githubusercontent.com/Microsoft/nav-arm-templates/master/CleanupAfterDocker.ps1</a>
<pre>$ErrorActionPreference = "Stop"
$WarningActionPreference = "Stop"

# Specify which images to download
$ImagesToDownload = @("microsoft/dynamics-nav:generic"
                      "mcr.microsoft.com/businesscentral/onprem",
                      "microsoft/dynamics-nav:2018")

$navContainerHelperFolder = "C:\ProgramData\NavContainerHelper"

$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (!($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) {
    throw "This script must run with administrator privileges"
}

Write-Host "Checking Docker Service Settings..."
$dockerService = (Get-Service docker -ErrorAction Ignore)
if (!($dockerService)) {
    throw "Docker Service not found / Docker is not installed"
}

if ($dockerService.Status -ne "Running") {
    throw "Docker Service is $($dockerService.Status) (Needs to be running)"
}

$dockerInfo = (docker info)
$dockerOsMode = ($dockerInfo | Where-Object { $_.StartsWith('OSType: ') }).SubString(8)
if ($dockerOsMode -ne "Windows") {
    throw "Docker is not running Windows Containers"
}

$dockerRootDir = ($dockerInfo | Where-Object { $_.StartsWith('Docker Root Dir: ') }).SubString(17)
if (!(Test-Path $dockerRootDir -PathType Container)) {
    throw "Folder $dockerRootDir does not exist"
}

Write-Host -Foregroundcolor Red "This function will remove all containers, remove all images and clear the folder $dockerRootDir"
Write-Host -Foregroundcolor Red "The function will also clear the contents of $navContainerHelperFolder."
Write-Host -Foregroundcolor Red "Are you absolutely sure you want to do this? (This cannot be undone)"
Write-Host -ForegroundColor Red "Type Yes to continue:" -NoNewline
if ((Read-Host) -ne "Yes") {
    throw "Mission aborted"
}

Write-Host "Running Docker System Prune"
docker system prune -f

Write-Host "Removing all containers (forced)"
docker ps -a -q | % { docker rm $_ -f 2&gt; NULL }

Write-Host "Stopping Docker Service"
stop-service docker

Write-Host "Downloading Docker-Ci-Zap"
$dockerCiZapExe = Join-Path $Env:TEMP "docker-ci-zap.exe"
Remove-Item $dockerCiZapExe -Force -ErrorAction Ignore
(New-Object System.Net.WebClient).DownloadFile("https://github.com/jhowardmsft/docker-ci-zap/raw/master/docker-ci-zap.exe", $dockerCiZapExe)
Unblock-File -Path $dockerCiZapExe

Write-Host "Running Docker-Ci-Zap on $dockerRootDir"
Write-Host -ForegroundColor Yellow "Note: If this fails, please restart your computer and run this script again"
&amp; $dockerCiZapExe -folder $dockerRootDir

Write-Host "Removing Docker-Ci-Zap"
Remove-Item $dockerCiZapExe

Write-Host "Starting Docker Service"
Start-Service docker

if (Test-Path $navContainerHelperFolder -PathType Container) {
    Write-Host "Cleaning up $navContainerHelperFolder"
    Get-ChildItem $navContainerHelperFolder -Force | ForEach-Object { 
        Remove-Item $_.FullName -Recurse -force
    }
}

if ($ImagesToDownload) {
    Write-Host -ForegroundColor Green "Done cleaning up, pulling images for $os"

    $os = "ltsc2016"
    if ((Get-CimInstance win32_operatingsystem).BuildNumber -ge 17763) { $os = "ltsc2019" }

    Write-Host -ForegroundColor Green "Done cleaning up, pulling images for $os"
    # Download images needed
    $imagesToDownload.Split(',') | ForEach-Object {
        if ($_.EndsWith('-ltsc2016') -or $_.EndsWith('-1709') -or $_.EndsWith('-1803') -or $_.EndsWith('-ltsc2019') -or
            $_.EndsWith(':ltsc2016') -or $_.EndsWith(':1709') -or $_.EndsWith(':1803') -or $_.EndsWith(':ltsc2019')) {
            $imageName = $_
        } elseif ($_.Contains(':')) {
            $imageName = "$($_)-$os"
        } else {
            $imageName = "$($_):$os"
        }
        Write-Host "Pulling $imageName"
        docker pull $imageName
    }
}</pre>
I hope you find the explanation and the script helpful.

If the tool and the script doesn't work for you, there isn't much I can do about it.

&nbsp;

Enjoy

<span style="color: #0000ff"><em><strong>Freddy Kristiansen</strong></em></span>
<span>Technical Evangelist</span>]]></content:encoded>
    </item>
    <item>
      <title>Business Central Build Numbers</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/business-central-build-numbers</link>
      <pubDate>Wed, 05 Dec 2018 12:00:07 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4935</guid>
      <description><![CDATA[Now and then, I get questions about the build numbers - what do they all mean? I always try to tell...]]></description>
      <content:encoded><![CDATA[Now and then, I get questions about the build numbers - what do they all mean? I always try to tell people that they don't really need to know but for some reason, that just makes people more curious. So instead of answering the same question over and over again - here is what I know...

As you might know, I wrote <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/04/16/which-docker-image-is-the-right-for-you/" rel="noopener">this blog post</a> a while back, trying to describe that you shouldn't really care about the build numbers, you should always go for the latest unless instructed otherwise.

But let me try to shed some light over the version numbering of these Docker Images. In order to do so, I will have to talk about public and insider builds separately, but before I do that, let me shortly describe what they are doing in Windows (as our strategy isn't that different).
<h1>Windows Build numbers</h1>
Windows 10 and Windows Server uses the same build numbers, i.e. 10.0.&lt;build number&gt;.&lt;revision&gt;. The major and the minor version number doesn't change and the build numbers are public (<a target="_blank" href="https://www.microsoft.com/en-us/itpro/windows-10/release-information" rel="noopener">https://www.microsoft.com/en-us/itpro/windows-10/release-information</a>), the revision not so much.

Build 17763 is Windows 10 Fall Update 2018, also called 1809 and build 17763 is also Windows Server 2019.

Build 17134 is Windows 10 Spring Update 2018, also called 1803.

etc.

With Business Central we also use the major and the minor version numbers.
<h1>Business Central Build numbers</h1>
In October 2018 we shipped Business Central Fall 2018 release.
<h2>Fall 2018 release, update 19, 13.0, rtm</h2>
There are a lot of names for the Business Central Fall 2018 release, lets explain them:
<ul>
 	<li><strong>Fall 2018 release</strong> - obviously because the release is a major release from fall of 2018.</li>
 	<li>Internally (and sometimes externally) this is called <strong>update 19</strong> because it is month 19 since we started our online services and this the 19th update to our SaaS offerings.</li>
 	<li>Dynamics NAV 2018 was 11.0, Business Central Spring 2018 was 12.0, Business Central Fall 2018 is <strong>13.0</strong></li>
 	<li><strong>rtm</strong> is only used for the on-premises release of Business Central as this was the first release of that</li>
</ul>
Business Central Fall 2018 release is major release <strong>13</strong>, minor release <strong>0 </strong>and build number <strong>24630</strong> was the golden build.

Putting these numbers together gives <strong>13.0.24630.0</strong> which indeed is the version number for Business Central Fall 2018. If you want to pull the on-premises Docker image for this release you can use:
<pre>docker pull mcr.microsoft.com/businesscentral/onprem:13.0.24630.0[-country][-os]</pre>
where
<ul>
 	<li><strong>country</strong> is one of the countries shipped for on-premises: <strong>w1</strong>, <strong>at</strong>, <strong>au</strong>, <strong>be</strong>, <strong>ch</strong>, <strong>cz</strong>, <strong>de</strong>, <strong>dk</strong>, <strong>es</strong>, <strong>fi</strong>, <strong>fr</strong>, <strong>gb</strong>, <strong>is</strong>, <strong>it</strong>, <strong>na</strong>, <strong>nl</strong>, <strong>no</strong>, <strong>nz</strong>, <strong>ru</strong>, <strong>se</strong>. Default is <strong>w1</strong>.</li>
 	<li><strong>os</strong> is either <strong>ltsc2016</strong> or <strong>ltsc2019</strong>. Default is <strong>ltsc2016</strong>.</li>
</ul>
The on-premises release is also tagged with <strong>rtm</strong> instead of <strong>13.0.24630.0</strong>.

If you want to pull the sandbox  Docker image for this release you can use:
<pre>docker pull mcr.microsoft.com/businesscentral/sandbox:13.0.24630.0[-country][-os]</pre>
where
<ul>
 	<li>country  is one of the countries shipped online: <strong>w1</strong>, <strong>at</strong>, <strong>au</strong>, <strong>be</strong>, <strong>ca</strong>, <strong>ch</strong>, <strong>de</strong>, <strong>dk</strong>, <strong>ee</strong>, <strong>es</strong>, <strong>fi</strong>, <strong>fr</strong>, <strong>gb</strong>, <strong>hk</strong>, <strong>hr</strong>, <strong>is</strong>, <strong>it</strong>, <strong>jp</strong>, <strong>kr</strong>, <strong>lt</strong>, <strong>lv</strong>, <strong>mx</strong>, <strong>nl</strong>, <strong>no</strong>, <strong>nz</strong>, <strong>pl</strong>, <strong>pt</strong>, <strong>rs</strong>, <strong>se</strong>, <strong>si</strong>, <strong>tw</strong>, <strong>us</strong>. Default is <strong>w1</strong>.</li>
 	<li><strong>os</strong> is either <strong>ltsc2016</strong> or <strong>ltsc2019</strong>. Default is <strong>ltsc2016</strong>.</li>
</ul>
For sandbox builds you will also find a country called <strong>base</strong>. Currently, this is the on-premises w1 database running in sandbox mode. This image is build as foundation for all the country versions.

The sandbox release is not tagged with any extra release tags
<h2>Update 20, 13.1, cu1</h2>
One month after Business Central Fall 2018 release, in November, we shipped the first minor update. Internally, this is known as update 20. As it is a minor release, the minor part of the version number is updated to 1 and for on-premises we also call this cu1.

The build number for update 20 golden build was 25940 giving us this image for the onprem Docker image:
<pre>docker pull mcr.microsoft.com/businesscentral/onprem:13.1.25940.0[-country][-os]</pre>
and this image for the sandbox Docker image:
<pre>docker pull mcr.microsoft.com/businesscentral/sandbox:13.1.25940.0[-country][-os]</pre>
<strong>country</strong> and <strong>os</strong> have the same meaning as with the fall release, but we are likely to add new countries at minor updates as well.
<h2>Update 21, 13.2, cu2</h2>
In December 2018, we will ship update 21. Another minor release, as cu2 on-premises. I do not have the build numbers for this yet, but you probably get the picture by now.
<h2>What about revisions?</h2>
For Business Central online, we also ship revisions or hotfixes. These hotfixes will keep the same Major and Minor version number and also the same build number. It will however add a revision number. The revision number is not a counter starting from zero - it is an internal build number added as revision number to indicate that it is a newer revision.

We do NOT build Docker images of all these revisions as we also don't roll out all revisions to our Business Central online servers.

For Fall release, we did create Docker images of 3 revisions: 24844, 25242 and 25789. You probably already guessed that this means that the latest sandbox Docker image you can get from update 19/13.0 is:
<pre>docker pull mcr.microsoft.com/businesscentral/sandbox:13.0.24630.25789[-country][-os]</pre>
For update 20, we have created Docker images of 2 revisions: 26108 and 26323, meaning that currently the latest sandbox Docker image available is:
<pre>docker pull mcr.microsoft.com/businesscentral/sandbox:13.1.25940.26323[-country][-os]</pre>
which is the same version you get by using:
<pre>docker pull mcr.microsoft.com/businesscentral/sandbox:[country][-][os]</pre>
These images are ONLY available for sandbox - we will not release revisions of the on-premises Docker images.
<h1>Build numbers of insider builds</h1>
If you are in the ReadyToGo program, you have access to Microsoft Collaborate and should have access to our insider builds.

<em><strong>Note:</strong> When you signed up for ReadyToGo, you signed a Non Disclosure Agreement (NDA) preventing you from telling about what you see and find in the insider builds. We (Microsoft) want to be the one disclosing our new features, but we would like you to know about it early and be ready with your offerings before we ship. You should be working on finding ways to leverage the innovations in the product instead of blogging about them.</em>

Insider builds come in two flavours: <strong>bcsandbox</strong> and <strong>bcsandbox-master</strong>
<ul>
 	<li><strong>bcsandbox</strong> contains functionality, which will ship in our<strong> next minor release</strong>. (While writing this blog post, this is January)</li>
 	<li><strong>bcsandbox-master</strong> contains functionality, which will ship in our<strong> next major release</strong>. (While writing this blog post, this is Spring 2019)</li>
</ul>
Now you might wonder why the functionality from bcsandbox won't be in the December release (13.2 / cu2)? The reason is simple, we branched out for the 13.2 / cu2 release a few days ago and are running final tests and quality assurance before shipping - this does take some time.

Even more confusing is it, that the version number of the current <strong>bcsandbox insider build</strong> (right now) is <strong>13.2.26500.0</strong>. The reason is again simple, we haven't updated the version number yet in the branch.

And to complete the confusion. The version number of the current <strong>bcsandbox-master insider build</strong> (right now) is<strong> 14.0.26496.0</strong>, indicating that <strong>bcsandbox-master</strong> is older than <strong>bcsandbox</strong>. The reason is as usual simple, the build number is a number series shared between all branches (Dynamics NAV 2017 and up) and it just means that the daily bcsandbox-master build was build before the daily bcsandbox build. You might have noticed that the Major and Minor version number has been updated to 14.0 though.

Revision is always <strong>0</strong> as we are not shipping the insider builds and the build number will just be updated.

If you are working on an <strong>app for AppSource</strong>, you should consider setting up <strong>daily builds</strong> checking that your app doesn't break on the next minor update (<strong>bcsandbox</strong>). You should also consider setting up <strong>weekly builds</strong> to check that your app doesn't break on the next major update (<strong>bcsandbox-master</strong>). This is one of the next blog posts in the CI/CD series.

Remember that you will always get the latest bcsandbox image by using:
<pre>docker pull bcinsider.azurecr.io/bcsandbox:[country][-os]</pre>
and the latest bcsandbox-master by using:
<pre>docker pull bcinsider.azurecr.io/bcsandbox-master:[country][-os]</pre>
For the list of countries, available you should check Microsoft Collaborate (until we ship)
<h1>More version numbers</h1>
If you run docker inspect on a Docker image, you will under the Labels section under Config find not one, not two, not three, but <strong>four version numbers</strong>. Before getting that question, let me also describe the meaning of these
<pre>     "osversion": "10.0.17763.134"
     "tag": "0.0.8.0"
     "version": "14.0.26445.0"
     "platform": "13.0.26325.0"</pre>
<strong>osversion</strong> is the version of the Operating System. Which Windows Server Core version was used to build this image. Read more <a target="_blank" href="https://www.microsoft.com/en-us/itpro/windows-10/release-information" rel="noopener">here</a>.

<strong>tag</strong> is the version of the Generic Image used to build this mage. You will find the source for the generic image <a target="_blank" href="https://github.com/microsoft/nav-docker" rel="noopener">here</a> together with information about what's included in the version.

<strong>version</strong> is the version of Business Central. This version can be used as the tag to pull this image at a later time. This is the version number this blog post is all about.

<strong>platform</strong> is the version number of the platform (version of the Business Central binaries). These are following a different version numbering and the same platform version might be used for several releases of Business Central (especially on insider builds). We are likely to specify on our <a target="_blank" href="https://aka.ms/moderndevtools" rel="noopener">monthly developer preview blog posts</a> which platform version is needed for the features included and we need to know the platform version when you ask questions on <a target="_blank" href="https://github.com/Microsoft/al/issues" rel="noopener">AL issues</a>. Note also that when an issue has been fixed, the AL Bot will add a reply to the issue with a text like this:

<em>“ The fix for this issue has been checked in to the master branch. It will be available in the bcinsider.azurecr.io/bcsandbox-master Docker image starting <strong>from platform build number xxxxx</strong>.”</em>

This means that you can check whether the fix is in your version of docker image. Unfortunately we cannot give any build version or date when this the fix will be in bcsandbox-master.
<h1>Now you know... - but do you really need the info?</h1>
If you are developing a per tenant customization for an online tenant, you should always be using the <strong>latest</strong> public sandbox version - as this is what is being rolled out on our online servers. If your extension doesn't run on the latest sandbox, it will not run on an online tenant.

If you are developing an app for <strong>AppSource</strong>, you should be developing either on the <strong>latest</strong> public sandbox version or using insider builds. If you are using insider builds you should <strong>follow the insider builds (at least monthly)</strong>.

If you are lifting a Dynamics NAV vertical (<a target="_blank" href="https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/readiness/readiness-embed-apps" rel="noopener">https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/readiness/readiness-embed-apps</a>) you might need to use a<strong> specific build</strong>, but you should be in contact with Microsoft on which build to use.

If you are developing for on-premises, they should use <strong>rtm</strong> or <strong>cu1</strong> tags - not the version numbers and you should use the onprem container, NOT the sandbox containers. Here you also have the option of downloading the DVD.

&nbsp;
<p style="margin: 0px 0px 10px;color: #333333;text-indent: 0px;letter-spacing: normal;font-family: 'Segoe UI', Tahoma, Arial, 'Helvetica Neue', Helvetica, sans-serif;font-size: 14px;font-style: normal;font-weight: 400;background-color: #ffffff">Enjoy</p>
<p style="margin: 0px 0px 10px;color: #333333;text-indent: 0px;letter-spacing: normal;font-family: 'Segoe UI', Tahoma, Arial, 'Helvetica Neue', Helvetica, sans-serif;font-size: 14px;font-style: normal;font-weight: 400;background-color: #ffffff"><span style="color: #0000ff"><strong><em>Freddy Kristiansen</em></strong></span>
Technical Evangelist</p>]]></content:encoded>
    </item>
    <item>
      <title>NAV and Business Central Docker images now available for Windows Server 2019</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/nav-and-business-central-docker-images-now-available-for-windows-server-2019</link>
      <pubDate>Sat, 01 Dec 2018 01:21:57 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4885</guid>
      <description><![CDATA[As of today, all NAV and Business Central Docker images are available for Windows Server 2019 as...]]></description>
      <content:encoded><![CDATA[As of today, all NAV and Business Central Docker images are available for Windows Server 2019 as well as for Windows Server 2016!

It doesn't sound like a big deal, but believe me, it is...:-)
<h1>What does that mean?</h1>
As you probably know, Microsoft ships major updates to Windows every 6 months and every 2-3 years a version will be marked as <strong>LTSC</strong> (Long Term Servicing Channel).

For Windows Server, only the LTSC builds will include a UI, which allows you to connect to the Desktop of the Server. All updates between the LTSC releases will only ship as new versions of Windows Server Core, giving you only a Command prompt and/or PowerShell prompt.

You can read more about the Windows Server Semi Annual Channel <a target="_blank" href="https://docs.microsoft.com/en-us/windows-server/get-started/semi-annual-channel-overview" rel="noopener">here</a> and what that means to Windows 10 <a target="_blank" href="https://docs.microsoft.com/en-us/windows/deployment/update/waas-overview" rel="noopener">here</a>.

Up until today, <strong>specific</strong> images containing <strong>NAV</strong> or <strong>Business Central</strong> have only been available based on <strong>Windows Server Core 2016</strong>.

<strong>Generic</strong> images (images, which can be used to run <strong>NAV</strong> or <strong>Business Central</strong> if you have an install media or files extracted from another image) are available for most<strong> Semi annual builds</strong> and you can select to use these by using<strong> -useBestContainerOS</strong> on <strong>New-NavContainer</strong>. <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/10/24/windows-10-and-docker-images-for-business-central-nav/" rel="noopener">This</a><span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px"> blog post explains how to do this.</span>

As of today, specific images containing <strong>NAV</strong> or <strong>Business Central</strong> are also available based on <strong>Windows Server Core 2019</strong>.
<h1>Image Tags</h1>
Some sample image names for NAV and Business Central:
<ul>
 	<li>microsoft/dynamics-nav:2018-cu4-dk</li>
 	<li>mcr.microsoft.com/businesscentral/onprem:cu1-de</li>
 	<li>mcr.microsoft.com/businesscentral/sandbox:13.1.25940.26323</li>
</ul>
If you use <strong>docker pull</strong> and <strong>docker run</strong> on these images, you will get a <strong>ltsc2016</strong> (Windows Server Core 2016) version of the images. That is the same behavior as before today.

If you append <b>-ltsc2016</b>, you will get exactly the same images:
<ul>
 	<li>microsoft/dynamics-nav:2018-cu4-dk-ltsc2016</li>
 	<li>mcr.microsoft.com/businesscentral/onprem:cu1-de<span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px">-ltsc2016</span></li>
 	<li>mcr.microsoft.com/businesscentral/sandbox:13.1.25940.26323<span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px">-ltsc2016</span></li>
</ul>
If you append <strong>-ltsc2019</strong>, you will get the same versions of NAV/Business Central, but based on <strong>ltsc2019</strong> (Windows Server Core 2019)
<ul>
 	<li>microsoft/dynamics-nav:2018-cu4-dk-ltsc2019</li>
 	<li>mcr.microsoft.com/businesscentral/onprem:cu1-de<span>-ltsc2019</span></li>
 	<li>mcr.microsoft.com/businesscentral/sandbox:13.1.25940.26323<span>-ltsc2019</span></li>
</ul>
Using docker pull and docker run, you need to determine yourself which version of the image to pull and run.
<h1>Compatibility</h1>
One reason for using ltsc2019 images is compatibility. It is my experience, that the further away we came from Windows Server 2016, the more issues we would see when people were running Windows 10 and using ltsc2016 images. Almost all issues that I have been unable to resolve for people have been due to Windows 10 1803 running Docker and ltsc2016 images.
<h1>Image Size</h1>
Another reason for using ltsc2019 images is image size. After having pulled a number of images, we can inspect the sizes they occupy:

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/images.png"><img width="1024" height="335" class="alignnone size-large wp-image-4895" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/images-1024x335.png" /></a>
<ul>
 	<li>On WindowsServerCore, the ltsc2019 version is 7.02GB smaller.</li>
 	<li>The Generic layer on ltsc2019 is another 2.39GB smaller than the ltsc2016 equivalent, making it 9.41GB smaller in total.</li>
 	<li>Even the W1 layer is smaller. 300MB is saved on ltsc2019, making the image 9.71GB smaller in total.</li>
 	<li>The country layer is only a fraction smaller. Still the combined savings of 9.73GB is massive.</li>
</ul>
Note that these sizes are after extraction of the compressed images. The download sizes are way smaller, but the difference between the download size of ltsc2016 images and ltsc2019 images are even bigger.

As an example, the download size of the ltsc2016 version of NAV 2018 CU5 DK is <span>8,159,631,982</span> bytes and the ltsc2019 equivalent is <span>3,208,188,273</span> bytes. The ltsc2016 image is more than 2½ times the size of the ltsc2019 image.
<h1>NavContainerHelper 0.4.2.2</h1>
A new version of <strong>NavContainerHelper</strong> will try to help you get the right image for your host. If you specify that you want to use the Danish CU1 version of Business Central on prem, you would specify <strong>mcr.microsoft.com/businesscentral/onprem:cu1-dk</strong> as the imagename. With NavContainerHelper, you should still just specify the imagename like this and NavContainerHelper will then help you determine the best container OS.

The strategy for NavContainerHelper to determine which imagename to use is as follows:
<ol>
 	<li>If the imagename doesn't specify platform (ltscxxxx), then calculate the best imagename (append -ltsc2016 for Windows Server 2016 or -ltsc2019 for Windows Server 2019)</li>
 	<li>If you have not specified -alwaysPull, use the best imagename or the imagename in that order if they exist on the host</li>
 	<li>If the images didn't exist on the host or you have specified -alwaysPull, try to pull the best imagename and if that fails, revert back to the imagename specified</li>
</ol>
You can also specify -useBestContainerOS if you want to allow NavContainerHelper to use a generic image if that is better, in fact I recommend that you always do that. This allows NavContainerHelper to spin up a better generic image matching your OS in the future as well. -useBestContainerOS won't do anything if you are already using the best container os for your host.
<h1>ARM templates</h1>
When using <a target="_blank" href="http://aka.ms/getbc" rel="noopener">http://aka.ms/getbc</a>, <a target="_blank" href="http://aka.ms/getnav" rel="noopener">http://aka.ms/getnav</a> or any of the other ARM templates to spin up Azure VMs with Business Central or NAV, you will now have an option to select your operating system:

<a href="https://msdnshared.blob.core.windows.net/media/2018/12/os1.png"><img width="721" height="110" class="alignnone size-full wp-image-4915" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/12/os1.png" /></a>

Since the ARM templates uses the NavContainerHelper to create the container, you do not have to specify platform version after the image name. Just specify <strong>mcr.microsoft.com/businesscentral/onprem:cu1-dk</strong><span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px"> </span>and you will get the version matching the operating system you selected.

The total time it takes to spin up the Azure VM, initialize the VM, pull the image and run the image varies depending on a number of things, but I did a test of spinning up the same image on these 3 host operating systems and the total time for initialization was:
<ol>
 	<li>Windows Server 2016 = 42 minutes</li>
 	<li>Windows Server 2019 = 22 minutes</li>
 	<li>Windows Server 2019 with Containers = 20 minutes</li>
</ol>
The primary difference between 2 and 3 is, that #3 has docker pre-installed and the WindowsServerCore 1809 pre-downloaded. This normally works just fine, but I have kept option #2 if for some reason #3 isn't running the latest Docker or isn't updated. #1 is kept as an option until we decide to stop building ltsc2016 images.
<h1>Issues?</h1>
If you encounter any issues with the images, with NavContainerHelper or with the ARM templates, please file the issues on GitHub:
<ul>
 	<li><a target="_blank" href="https://github.com/Microsoft/navcontainerhelper/issues" rel="noopener">https://github.com/Microsoft/navcontainerhelper/issues</a></li>
 	<li><a target="_blank" href="https://github.com/Microsoft/nav-arm-templates/issues" rel="noopener">https://github.com/Microsoft/nav-arm-templates/issues</a></li>
 	<li><a target="_blank" href="https://github.com/Microsoft/nav-docker/issues" rel="noopener">https://github.com/Microsoft/nav-docker/issues</a></li>
</ul>
It is much better than emailing me. An email might disappear in the inbox, a GitHub issue will also notify me with an email and it will be there for other people to see, comment on and learn from.
<h1>What next</h1>
First of all - it's late Friday evening - or rather early Saturday morning. I will enjoy my weekend and then next week I will continue the blog post series on Continuous Integration and Continuous Delivery - part #6 coming up...

&nbsp;

Enjoy

<em><strong>Freddy Kristiansen</strong></em>
<span style="color: #0000ff">Technical Evangelist</span>]]></content:encoded>
    </item>
    <item>
      <title>Developing Business Central Extensions (part 5) - YAML</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/developing-business-central-extensions-part-5</link>
      <pubDate>Sat, 17 Nov 2018 09:17:36 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4775</guid>
      <description><![CDATA[This is part 5 of a blog post series. Part 1 contains all the prerequisites, part 2 is about cloning...]]></description>
      <content:encoded><![CDATA[<span>This is part 5 of a blog post series. <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-1/" rel="noopener">Part 1</a> contains all the prerequisites, <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-2/" rel="noopener">part 2</a> is about cloning the project and get your sandbox environment up running, <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-3/" rel="noopener">part 3</a> is about build agents, and building your project in Azure DevOps and <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/13/developing-business-central-extensions-part-4/" rel="noopener">part 4</a> is about branch policies and working with VSCode and Git.</span>
<h1>YAML, or not YAML, that's the question!</h1>
In part 3 of this blog post series, we created a build pipeline based on a file called CI.yml. This file is using YAML to describe the build pipeline and is actually very easy to read if you open it in VS Code.

I actually did not start out using YAML, I started out using the visual designer to create and work on my build pipeline and for some time, I was arguing with myself which approach would be the best. The Visual Designer has some obvious advantages for newbies like me, but YAML seemed to be the right way when I want to have the build pipeline saved as part of the repo (as code) and be able to transfer this to other repositories.

There are other reasons why one would need to use the Visual Designer, so it is really up to you whether you want to go with YAML or the Visual Designer.

I have included a few reasons for and reasons against using YAML, then you can decide for yourself. In this blog post I will also describe how you within 15 minutes can convert the CI YAML pipeline to a Visual Designer Pipeline - it is really just a lot of Copy/Paste work.
<h2>Reasons for using YAML</h2>
<ul>
 	<li>You like code and markup editing over visual designer editing (wysiwyg)</li>
 	<li>Pipeline is checked in as a file in code and follows branching and forking</li>
 	<li>Changes to code and changes to pipeline can be checked in together as part of a pull request</li>
 	<li>I have not found a good way to reuse a build pipeline created using the visual designer across projects
<ul>
 	<li>The export format from the Visual Designer (.json) is very much tied to the repository and the branch and doesn't seem suited to be used as a template</li>
 	<li>Saving the pipeline as a template also doesn't make the template available in other projects</li>
</ul>
</li>
</ul>
<h2>Reasons against using YAML</h2>
<ul>
 	<li>You like visual designer editing (wysiwyg) over code and hate markup editing</li>
 	<li>If your source code isn't stored in Azure DevOps (could be GitHub, Subversion, Bitbucket or external Git), then you cannot use Yaml</li>
 	<li>If a change to the code requires a change in the pipeline, you will have to hand-carry the change into the repo</li>
 	<li>Steep learning curve creating your first YAML pipeline (if you don't have the sample from the template repo)</li>
</ul>
Whether you use the Visual Designer or YAML, you can save the pipeline as a template and reuse it, but it is still not reusable in other repositories.
<h1>YAML isn't YAML</h1>
As I wrote earlier, I started out using the Visual Designer and switched to YAML.

I thought conversion would be as easy as clicking <strong>View YAML</strong> on my Visual Designer pipeline but when things sounds to good to be true,. they usually are. There seem to be a bug (or maybe it is only when using PowerShell tasks). The YAML I got out of my Visual pipeline couldn't be imported later. Instead of waiting for somebody to fix the bug, I did a manual conversion and now my master is in YAML.

A full description of the YAML format is available here:

<a target="_blank" href="http://aka.ms/yaml" rel="noopener">http://aka.ms/yaml</a>

very very helpful when converting and coding in YAML.
<h1>CI.yml</h1>
While writing this blog post, the CI.yml file looks like this:
<div>
<pre><span>resources</span><span>:</span>
<span>- </span><span>repo</span><span>: </span><span>self
</span><span>
steps</span><span>:</span>
<span>- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Defaulting container version'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>errorActionPreference</span><span>: </span><span>silentlyContinue</span>
<span>    </span><span>targetType</span><span>: </span><span>inline</span>
<span>    </span><span>script</span><span>: </span><span>'if ("$(version)" -eq "")  { Write-Host "Version not defined, using current container version"; Write-Host "##vso[task.setvariable variable=version]current" } else { Write-Host "Using $(version) container version" }'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Login to bcinsider repository'</span>
<span>  </span><span>condition</span><span>: </span><span>and(succeeded(),or(eq(variables['version'],'nextminor'),eq(variables['version'],'nextmajor')))</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>inline</span>
<span>    </span><span>script</span><span>: </span><span>'docker login "bcinsider.azurecr.io" -u "$(bcInsiderUsername)" -p "$(bcinsiderPassword)"'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Install NavContainerHelper'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Install-NavContainerHelper.ps1'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Create Build Container'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Create-Container.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -licenseFile "$(LicenseFile)"'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Compile App'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Compile-App.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -buildProjectFolder "$(Build.Repository.LocalPath)" -buildArtifactFolder "$(Build.ArtifactStagingDirectory)" -appFolders @("app")'</span>
<span>    </span><span>failOnStderr</span><span>: </span><span>true</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Sign App'</span>
<span>  </span><span>condition</span><span>: </span><span>and(succeeded(),ne(variables['CodeSignPfxFile'],''),ne(variables['CodeSignPfxPassword'],''))</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Sign-App.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -buildArtifactFolder "$(Build.ArtifactStagingDirectory)" -appFolders @("app") -PfxFile (ConvertTo-SecureString -String "$(CodeSignPfxFile)" -AsPlainText -Force) -PfxPassword (ConvertTo-SecureString -String "$(CodeSignPfxPassword)" -AsPlainText -Force)'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Publish App'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Publish-App.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -buildArtifactFolder "$(Build.ArtifactStagingDirectory)" -appFolders @("app") -skipVerification:("$(CodeSignPfxFile)$(CodeSignPfxPassword)" -eq "")'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Import TestToolkit'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Import-TestToolkit.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force)))'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Compile Test App'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Compile-App.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -buildProjectFolder "$(Build.Repository.LocalPath)" -buildArtifactFolder "$(Build.ArtifactStagingDirectory)" -appFolders @("test")'</span>
<span>    </span><span>failOnStderr</span><span>: </span><span>true</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Publish Test App'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Publish-App.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -buildArtifactFolder "$(Build.ArtifactStagingDirectory)" -appFolders @("test") -skipVerification'</span>
<span>
- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Run Tests'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Run-Tests.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -TestResultsFile (Join-Path "$(System.DefaultWorkingDirectory)" "TestResults.xml") -test "unittests"'</span>
<span>
- </span><span>task</span><span>: </span><span>PublishTestResults@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Publish Test Results'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>testResultsFormat</span><span>: </span><span>XUnit</span>
<span>    </span><span>testResultsFiles</span><span>: </span><span>TestResults.xml
</span>
<span>- </span><span>task</span><span>: </span><span>PublishBuildArtifacts@1</span>
<span>  </span><span>displayName</span><span>: </span><span>'Publish Artifact: app'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>PathtoPublish</span><span>: </span><span>'$(Build.ArtifactStagingDirectory)\app'</span>
<span>    </span><span>ArtifactName</span><span>: </span><span>app
</span>
<span>- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Remove Build Container'</span>
<span>  </span><span>condition</span><span>: </span><span>always()</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Remove-Container.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force)))'</span></pre>
</div>
Very straightforward. As you can see, the YAML file consists of a series of tasks.

Every task has a type, a version, a display name, conditions and a number of properties.

If the Task is a PowerShell task, then it can be an inline script, meaning that it contains the PowerShell code directly or it can be a filePath, where it references a PowerShell script in the repository. If you are using filePath, you can specify a number of parameters to transfer to the script.

The script will have to have a Param section, matching the parameters from the build pipeline.

Example, the Param section of the Create-Container script looks like:
<div>
<pre><span>Param</span><span>(</span>
<span>    [</span><span>ValidateSet</span><span>(</span><span>'AzureDevOps'</span><span>,</span><span>'Local'</span><span>)]</span>
<span>    [</span><span>string</span><span>] </span><span>$run</span><span> </span><span>=</span><span> </span><span>"AzureDevOps"</span><span>,</span>
<span>    [</span><span>ValidateSet</span><span>(</span><span>'current'</span><span>,</span><span>'nextminor'</span><span>,</span><span>'nextmajor'</span><span>)]</span>
<span>    [</span><span>string</span><span>] </span><span>$version</span><span> </span><span>=</span><span> </span><span>"current"</span><span>,</span>
<span>    [</span><span>ValidateSet</span><span>(</span><span>'bld'</span><span>,</span><span>'dev'</span><span>)]</span>
<span>    [</span><span>string</span><span>] </span><span>$type</span><span> </span><span>=</span><span> </span><span>"bld"</span><span>,</span>
<span>    [</span><span>Parameter</span><span>(</span><span>Mandatory</span><span>=</span><span>$true</span><span>)]</span>
<span>    [</span><span>pscredential</span><span>] </span><span>$credential</span><span>,</span>
<span>    [</span><span>String</span><span>] </span><span>$licenseFile</span><span> </span><span>=</span><span> </span><span>""</span>
<span>)</span></pre>
</div>
Matching the parameters transferred in CI.yml:
<pre>-version "$(version)"
-credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) 
-licenseFile "$(LicenseFile)"'</pre>
It is possible to run things in parallel, but in our case, the pipeline is really a single threaded thing, which cannot be parallelized.
<h1>Multiple Apps</h1>
If your project consists of multiple apps, which have dependencies, the idea is to catch these dependencies in the build pipeline. We might at a later time add functionality to automatically detect and build dependencies but for now, you will have to capture these in the pipeline.

You can transfer several app folders to the Compile App tasks (if the apps are not inter-dependent) or you can have multiple Compile App steps if the apps depend on other apps, which then have to be published (much like the App and the Test App).

No, it is not ideal, but it is possible.
<h1>"Converting" CI.yml to a pipeline in the Visual Designer</h1>
It isn't really converting, it is creating a new pipeline using the visual designer and copying the information from the YAML file.

I have decided NOT to include a CI.json (pipeline export format) in the template repository, instead you will have to create a new empty pipeline using the visual designer, link the Azure Key Vault variable group and add a number of tasks to the pipeline.
<h2>The first step</h2>
Let's have a look at the very first step in the pipeline, which task is to set the version pipeline variable to current, if it hasn't been set already:
<div>
<pre><span>- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Defaulting container version'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>errorActionPreference</span><span>: </span><span>silentlyContinue</span>
<span>    </span><span>targetType</span><span>: </span><span>inline</span>
<span>    </span><span>script</span><span>: </span><span>'if ("$(version)" -eq "")  { Write-Host "Version not defined, using current container version"; Write-Host "##vso[task.setvariable variable=version]current" } else { Write-Host "Using $(version) container version" }'</span></pre>
</div>
In the Visual Designer you add a Task to the Agent Job and search for PowerShell (not Azure PowerShell, not Service Fabric PowerShell, not PowerShell on Target Machines - just PowerShell). Add this task and fill out the properties:
<ul>
 	<li>The number after PowerShell in the - task line determines the version of PowerShell (here 2)</li>
 	<li>The displayName goes into Display Name</li>
 	<li>errorActionPreference (defaults to stop) goes into ErrorActionPreference</li>
 	<li>If targettype is inline, type inline is selected and the script goes into the scripts editor.</li>
</ul>
The above step in the Visual Designer will look something like this:
<div><a href="https://msdnshared.blob.core.windows.net/media/2018/11/visual-1.png"><img width="549" height="559" class="alignnone size-full wp-image-4805" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/visual-1.png" /></a></div>
<h2>Conditions</h2>
The second step in the pipeline is logging into the bcinsider repo, if you are building against nextminor or nextmajor. It looks like
<div>
<pre><span>- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Login to bcinsider repository'</span>
<span>  </span><span>condition</span><span>: </span><span>and(succeeded(),or(eq(variables['version'],'nextminor'),eq(variables['version'],'nextmajor')))</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>inline</span>
<span>    </span><span>script</span><span>: </span><span>'docker login "bcinsider.azurecr.io" -u "$(bcInsiderUsername)" -p "$(bcinsiderPassword)"'</span></pre>
</div>
This task is also a PowerShell task and the only difference to the first step is that this step only executes if the conditions are met.
The conditions here are:
<ul>
 	<li>If all previous steps succeeded and version is either nextminor or nextmajor.</li>
</ul>
Then we want to login to the bcinsider registry using the username and password from the key vault.
In the Visual Designer, you find the conditions under Control Options:
<div><a href="https://msdnshared.blob.core.windows.net/media/2018/11/conditions1.png"><img width="546" height="352" class="alignnone size-full wp-image-4825" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/conditions1.png" /></a></div>
<h2>Script Path</h2>
The third step in the pipeline will install the NavContainerHelper module if not already installed. It looks like
<div>
<pre><span>- </span><span>task</span><span>: </span><span>PowerShell@2
</span><span>  </span><span>displayName</span><span>: </span><span>'Install NavContainerHelper'
</span><span>  </span><span>inputs</span><span>:
</span><span>    </span><span>targetType</span><span>: </span><span>filePath
</span><span>    </span><span>filePath</span><span>: </span><span>'scripts\Install-NavContainerHelper.ps1'
</span></pre>
</div>
Again, a PowerShell task, but in this case, it is not an inline script, instead the targetType is a filePath and the FilePath refers to a script in the script folder. The conditions are default, meaning that all previous tasks have to succeed before this task is run.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/scriptpath.png"><img width="547" height="155" class="alignnone size-full wp-image-4835" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/scriptpath.png" /></a>
<h2>Arguments</h2>
The forth step in the pipeline is creating the build container. This also uses a filePath but this one comes with arguments:
<div>
<pre><span>- </span><span>task</span><span>: </span><span>PowerShell@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Create Build Container'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>targetType</span><span>: </span><span>filePath</span>
<span>    </span><span>filePath</span><span>: </span><span>'scripts\Create-Container.ps1'</span>
<span>    </span><span>arguments</span><span>: </span><span>'-version "$(version)" -credential ([PSCredential]::new("$(Username)", (ConvertTo-SecureString -String "$(Password)" -AsPlainText -Force))) -licenseFile "$(LicenseFile)"'</span></pre>
</div>
The arguments are using the exact same syntax as the aguments in the Visual Designer, just remove the '.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/arguments.png"><img width="553" height="251" class="alignnone size-full wp-image-4845" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/arguments.png" /></a>
<h2>Other properties</h2>
For the remaining tasks, a few other properties are used, but these maps easily into the Visual Designer as well (like FailOnStderr -&gt; Fail on Standard Error under Advanced).

Note that the Remove Build Container task is using always() in conditions, meaning that it will run even if a previous task has failed, even if the build was cancelled.
<h2>Publishing Test Results</h2>
After running the tests, the tests results needs to be published. As the run tests task will store the test results in XUnit format in the file TestResults.xml, the task for publishing the tests looks like:
<div>
<pre><span>- </span><span>task</span><span>: </span><span>PublishTestResults@2</span>
<span>  </span><span>displayName</span><span>: </span><span>'Publish Test Results'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>testResultsFormat</span><span>: </span><span>XUnit</span>
<span>    </span><span>testResultsFiles</span><span>: </span><span>TestResults.xml</span></pre>
</div>
In the Visual Designer, it is really just as simple

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/publishtests.png"><img width="532" height="370" class="alignnone size-full wp-image-4855" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/publishtests.png" /></a>
<h2>Publishing Build Artifact</h2>
If everything succeeded, we need to publish the build artifact (our app) where it can be accessed after the build. For that we use the Publish Build Artifact task
<div>
<pre><span>- </span><span>task</span><span>: </span><span>PublishBuildArtifacts@1</span>
<span>  </span><span>displayName</span><span>: </span><span>'Publish Artifact: app'</span>
<span>  </span><span>inputs</span><span>:</span>
<span>    </span><span>PathtoPublish</span><span>: </span><span>'$(Build.ArtifactStagingDirectory)\app'</span>
<span>    </span><span>ArtifactName</span><span>: </span><span>app</span></pre>
</div>
Like when publishing tests results, the Visual Designer task is just as easy

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/publishbuild.png"><img width="555" height="330" class="alignnone size-full wp-image-4865" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/publishbuild.png" /></a>
<h1>What's next</h1>
This blog post really didn't get us any further in the process of setting up CI/CD, but it does explain why I selected YAML as the format of pipelines and when we get to create integration builds with minor or major builds of Business Central a little later, these things should be clear.

In the next post, I will cover how to automatically deploy the daily build of your extension to an Azure VM, where you can demo or test the latest version at all times.

&nbsp;

Enjoy

<em><strong>Freddy Kristiansen</strong></em>
<span style="color: #0000ff">Technical Evangelist</span>]]></content:encoded>
    </item>
    <item>
      <title>Windows Server 2019 and SQL Server 2017</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/windows-server-2019-and-sql-server-2017</link>
      <pubDate>Thu, 15 Nov 2018 11:27:08 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4795</guid>
      <description><![CDATA[Windows Server 2019 (1809) finally shipped (again) and with that we have a new LTSC (Long Term...]]></description>
      <content:encoded><![CDATA[Windows Server 2019 (1809) finally shipped (again) and with that we have a new LTSC (Long Term Servicing Channel). This means that we will begin creating images for Windows Server 2019 as soon as our infrastructure is ready for that.
<h1>-UseBestContainerOS</h1>
While waiting for 1809 images, you can use -UseBestContainerOS when running images with NavContainerHelper and it will automatically detect the best generic image to use for your host OS, whether that is Windows Server 2016, Windows Server 2019, Windows 10 1709, Windows 10 1803 or Windows 10 1809.

It will even detect that you are running an insider version of Windows Server and fall back to Windows Server 2019 generic image.
<h1>SQL Server 2017</h1>
With the release of Windows Server 2019, I tried to just rebuild the generic image based on the new Windows Server Core 1809 image. SQL Server 2016 has always been used in the images, but it did cause problems with 1809, so I decided to switch to SQL Server 2017, which installs and works.

A few issues though encountered during testing:
<h2>DAC missing</h2>
SQL Server 2016 came with a version of Dac Framework pre-installed. SQL Server 2017 doesn't. Dac was used for importing .bacpac files in the container. I have changed this code to do a Just-In-Time download and install of Dac Framework 18.0 when needed.
<h2>Export-NavApplication</h2>
<span style="color: #000025">When switching to multitenant setup, the container uses Export-NavApplication. This worked out of the box for SQL Server 2016, but for some reason SQL Server 2017 failed. After a lot of investigations, I found that setting -ServiceAccount on Export-NavApplication to <strong>NT AUTHORITY\SYSTEM</strong> and adding the user to the database solved the issue. This also required the Export-NavContainerDatabasesAsBacpac in NavContainerHelper to remove this user on export.</span>
<h2>Cmdlets expecting "old" version of DLLs</h2>
<span style="color: #000025">The Cmdlets for Service Tier administration in old NAV and Business Central versions are expecting earlier versions of SQL Server DLL's:</span>
<ul>
 	<li><span style="color: #000025">Microsoft.SqlServer.Smo</span></li>
 	<li><span style="color: #000025">Microsoft.SqlServer.SmoExtended</span></li>
 	<li><span style="color: #000025">Microsoft.SqlServer.ConnectionInfo</span></li>
 	<li><span style="color: #000025">Microsoft.SqlServer.SqlEnum</span></li>
</ul>
<span style="color: #000025">By placing dependentAssembly sections the AssemblyBinding section in PowerShell.exe.config in the container, specifying that any attempt to load old versions of these DLLs, should take the new version:</span>
<pre>      <span>&lt;dependentAssembly&gt; 
         &lt;assemblyIdentity name="Microsoft.SqlServer.Smo" publicKeyToken="89845dcd8080cc91"/&gt;
         &lt;bindingRedirect oldVersion="0.0.0.0-14.0.0.0" newVersion="14.0.0.0"/&gt;
      &lt;/dependentAssembly&gt;</span></pre>
<h2><span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px">This should make all CmdLets work with the new SQL Server.</span></h2>
<h2>Export-NavContainerDatabasesAsBacpac</h2>
<span style="color: #000025">This function is placed in the NavContainerHelper, but it also used to install a newer version of the Dac Framework (than the one included in SQL Server 2016). This function has been changed to also use the new version (18.0) and it will just re-use the version installed in the container if possible. This fix is included in NavContainerHelper 0.4.1.0.</span>
<h1>So, what's there now</h1>
You will need <strong>NavContainerHelper 0.4.1.0</strong> for supporting Windows Server 2019 and SQL Server 2017.

While writing this blog post, the generic images have been updated:
<ul>
 	<li><strong>microsoft/dynamics-nav:generic-ltsc2016</strong> is the generic image for Windows 10 1607, Windows ServerCore 1607 or Windows Server 2016. Based on Windows ServerCore ltsc2016 and the foundation of all <strong>ltsc2016</strong> NAV and Business Central containers</li>
 	<li><strong>microsoft/dynamics-nav:generic-1709</strong> is the generic image for Windows 10 1709 release or Windows ServerCore 1709. Based on Windows ServerCore 1709.</li>
 	<li><strong>microsoft/dynamics-nav:generic-1803</strong> is the generic image for Windows 10 1803 release or Windows ServerCore 1803. Based on Windows ServerCore 1803.</li>
 	<li><strong>microsoft/dynamics-nav:generic-ltsc2019</strong> is the generic image for Windows 10 1809, Windows ServerCore 1809 or Windows Server 2019. Based on Windows ServerCore 1809.</li>
</ul>
<span><strong>microsoft/dynamics-nav:generic</strong> still points to </span><strong>microsoft/dynamics-nav:generic-ltsc2016</strong>.

The version number of the new Generic image is <strong>0.0.8.0</strong>.
<h1>What will be there tomorrow</h1>
All future images (NAV and Business Central) will be build on the new ltsc2016 image with SQL Server 2017.
<h1>What will be there in a week from now</h1>
All NAV and Business Central images have been rebuilt to use the new ltsc2016 image with SQL Server 2017.
<h1>And finally, what will be there in a few weeks</h1>
All images are available as ltsc2019 as well as ltsc2016. In order to download the 1809 version you will have to append -ltsc2019 if using docker run. New-NavContainer will automatically detect that your host is Server 2019 and download the right image for that.

Remember that you can use ltsc2019 already today by specifying <strong>-useBestContainerOS</strong> to <strong>New-NavContainer</strong> or you can extract the files from a ltsc2016 container using <strong>Extract-FilesFromNavContainerImage</strong> and use them to run ltsc2019 image.

&nbsp;

All dates are subject to change...

&nbsp;

Enjoy

<em><strong>Freddy Kristiansen</strong></em>
<span style="color: #0000ff">Technical Evangelist</span>]]></content:encoded>
    </item>
    <item>
      <title>Developing Business Central Extensions (part 4) - Branch Policies</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/developing-business-central-extensions-part-4</link>
      <pubDate>Tue, 13 Nov 2018 09:30:40 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4535</guid>
      <description><![CDATA[This is part 4 of a blog post series. Part 1 contains all the prerequisites, part 2 is about cloning...]]></description>
      <content:encoded><![CDATA[This is part 4 of a blog post series. <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-1/" rel="noopener">Part 1</a> contains all the prerequisites, <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-2/" rel="noopener">part 2</a> is about cloning the project and get your sandbox environment up running and <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-3/" rel="noopener">part 3</a> is about build agents, and building your project in Azure DevOps.
<h1>Branch Policies</h1>
At this time, we have an Azure DevOps project with a build pipeline, which we can kick off manually. We have not setup any branch policies, meaning that anybody with write access to the repository can check in code, even code which will break the build and cause instability of your product.

It's time to setup branch policies to ensure quality.

Open your Azure DevOps organization (eg. https://dev.azure.com/&lt;organization&gt;/) and select <strong>Repos</strong> -&gt; <strong>Branches</strong>. Select the <strong>master</strong> branch, click the <strong>more actions</strong> symbol (<strong>...</strong>) and choose <strong>Branch Policies</strong>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/branches.png"><img width="1024" height="537" class="alignnone size-large wp-image-4545" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/branches-1024x537.png" /></a>

You should now be able to setup branch policies for your branch

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/Branch-Policies.png"><img width="1024" height="723" class="alignnone size-large wp-image-4555" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/Branch-Policies-1024x723.png" /></a>

The first thing you will notice is, that setting <strong>any required policy</strong> will <strong>enforce</strong> the use of <strong>pull requests</strong> and will <strong>disallow direct checkins</strong> to the <strong>master</strong> branch. It will also prevent the branch from unintended or evil deletion.

You can read much more about the policies here: <a target="_blank" href="https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies" rel="noopener">https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies</a>.

What I want, is to ensure that my CI build pipeline runs whenever somebody checks something in and that the checkin cannot be completed if the build isn't successful. This is called Build validation and by adding a build policy with our CI pipeline from <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-3/" rel="noopener">part 3</a>, we should be ready to go.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/build-policy.png"><img width="1024" height="723" class="alignnone size-large wp-image-4565" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/build-policy-1024x723.png" /></a>
<h1>Working with VS Code and Git</h1>
When working with a project under source code management in VS Code, you will notice some nice and very helpful features. I am using workspaces, meaning that I have added the app and the test folder to a workspace and I am opening that in VS Code, giving me a nice overview like:

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-1.png"><img width="1024" height="567" class="alignnone size-large wp-image-4575" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-1-1024x567.png" /></a>

Lets try to change the HelloWorld.al with a very simple change (add an exclamation mark to Hello World and Save the file)

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-2.png"><img width="1024" height="567" class="alignnone size-large wp-image-4585" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-2-1024x567.png" /></a>

You will see a number of things changing
<ul>
 	<li>The Source Control symbol shows that there is one modification</li>
 	<li>The HelloWorld.al file got a different color and is flagged with M (Modified)</li>
 	<li>In the bottom left corner you can see that you are working directly on the master branch and the * marks that you have unstaged changes</li>
</ul>
if you click the Source Control symbol to see your changes, you can select your changed files to see what changes you actually did

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/SCM-changes.png"><img width="1024" height="567" class="alignnone size-large wp-image-4595" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/SCM-changes-1024x567.png" /></a>

A few other things you could do to files
<ul>
 	<li>If you create a new file, it will be flagged with U (untracked), which means that this file is unknown to the repository.</li>
 	<li>If you delete a file, it will be deleted and disappear from the file list, but show up in the Source Control list of changes with a D (deleted)</li>
</ul>
The files listed under changes in the Source Control area are unstaged changed, meaning that you have not yet determined whether you want to checkin this change or not.

Clicking the + symbol next to the <strong>file</strong> will move the change to the staged changes. Clicking the + next to <strong>changes</strong> will move all changes to the staged changes.

Staged changes are changes you intend to commit, but haven't yet. The * marker next to the master branch (bottom left corner) changes to a +, meaning that you have staged changes.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-3.png"><img width="1024" height="567" class="alignnone size-large wp-image-4605" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-3-1024x567.png" /></a>

Note, that if I add another exclamation mark to the Hello World string and save the file, I will have the HelloWorld.al file in both staged and unstaged changes list:

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-4.png"><img width="1024" height="567" class="alignnone size-large wp-image-4615" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-4-1024x567.png" /></a>

This means that you are tracking changes - not files and now you have both a * and a + next to the master branch (bottom left corner).

You can now unstage your change, which will remove the change from the staged changes area, but not change your file. You unstaged change will just be both exclamation mark on the master branch and you will not have any staged changes.

You can also stage the second change, which will merge both changes and you will now have two exclamation marks in the staged changes area.

You can discard changes from unstaged changes. If you want to discard changes from staged changes, you will need to unstage the changes and then discard them.

So, staging or unstaging changes doesn't change any files, it only determines how Git looks at your changes.

Having staged my changes and checked that my changes are indeed ready to be checked in, it is time to Commit. Write a message in the <strong>Commit Message bar</strong> and press <strong>Ctrl+Enter</strong>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-5.png"><img width="1024" height="565" class="alignnone size-large wp-image-4625" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-5-1024x565.png" /></a>

After committing the changes you can close the comparison window and you will see

<u><span style="background-color: #bfe6ff;color: #000120"><a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-61.png"><img width="1024" height="565" class="alignnone size-large wp-image-4645" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-61-1024x565.png" /></a></span></u>

But wait… - did I just commit changes to the master branch? I shouldn't be able to do that, right? What's happening???

The answer is simple. As you probably know, Git is a distributed source control system and every developer machine has a full copy of the source tree. What we did here was to commit to my local copy of the master branch, but I didn't push my changes to the Azure DevOps repository yet.

Actually, you can see that in the bottom left corner there is an indicator signaling that you have one change, which needs to be pushed to Azure DevOps.

If other developers have made changes to the project, there might also be a number next to the Down Arrow.

So, in order for me to push my changes and pull changes from other people is to click the Synchronize button. When trying to synchronize, I get a strange message

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/cannot-push.png"><img width="468" height="120" class="alignnone size-full wp-image-4655" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/cannot-push.png" /></a>

Clicking the Open Git Log reveals the reason for this: <strong><span style="text-align: left;color: #333333;text-indent: 0px;letter-spacing: normal;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;text-decoration: none;cursor: text;float: none;background-color: #ffffff">Pushes to this branch are not permitted; you must use a pull request to update this branch.</span></strong>

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-7.png"><img width="1024" height="565" class="alignnone size-large wp-image-4665" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-7-1024x565.png" /></a>

So how do I create a pull request???

A pull request is a request to take changes from one branch and merge them into another branch. Problem is that I committed my changes to the local master branch. I need to uncommit those and put them in another branch. Fortunately, VS Code can help us here. In the Source Control section press the action menu (<strong>…</strong>) and select <strong>Undo Last Commit</strong>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-8.png"><img width="1024" height="565" class="alignnone size-large wp-image-4675" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-8-1024x565.png" /></a>

Undoing the commit will leave my changes unstaged again and my local master branch is clean.

Now, I can click the <strong>master</strong> branch symbol in the lower left corner to open the list of existing branches and select <strong>Create new branch</strong>. Enter the name of the new branch, press <strong>Enter</strong> and you should see

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-9.png"><img width="1024" height="565" class="alignnone size-large wp-image-4685" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-9-1024x565.png" /></a>

In the lower left corner you will now see that you are in the Exclamation branch and the icon to the right (an arrow into the cloud) means that your branch is purely local and have no representation in Azure DevOps. Clicking the arrow into the cloud symbol will create the branch in the cloud and you will now be able to see it in your Azure DevOps Repos -&gt; Branches page:

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/branches-2.png"><img width="1024" height="517" class="alignnone size-large wp-image-4695" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/branches-2-1024x517.png" /></a>

We can now press the <strong>+</strong> symbol next to the file to <strong>stage the changes</strong> and <strong>commit the changes</strong> to our <strong>local Exclamation branch</strong>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-10.png"><img width="1024" height="565" class="alignnone size-large wp-image-4705" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-10-1024x565.png" /></a>

Still the changes are purely local, but now, you can synchronize your changes to Azure DevOps.

After synchronizing changes to Azure DevOps, VS Code will look like

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-11.png"><img width="1024" height="565" class="alignnone size-large wp-image-4715" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/VS-Code-11-1024x565.png" /></a>

and viewing the branches again on Azure DevOps will show that our branch is <strong>one change ahead</strong> and we can now create a <b>pull request</b>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/branches-3.png"><img width="1024" height="517" class="alignnone size-large wp-image-4725" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/branches-3-1024x517.png" /></a>

There is an extension for VS Code, which allows you to do Pull Requests directly from VS Code, but I haven't tried this.

Read more here: <a target="_blank" href="https://code.visualstudio.com/blogs/2018/09/10/introducing-github-pullrequests" rel="noopener">https://code.visualstudio.com/blogs/2018/09/10/introducing-github-pullrequests</a>

Creating a pull request allows you to add code reviews, assign work item etc.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/branches-4.png"><img width="1024" height="533" class="alignnone size-large wp-image-4735" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/branches-4-1024x533.png" /></a>

After creating the pull request, a build is kicked off and you can monitor that build by clicking the build in progress link on the right side (under required policies)

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/branches-6.png"><img width="1024" height="533" class="alignnone size-large wp-image-4745" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/branches-6-1024x533.png" /></a>

You can select to set Auto-complete, meaning that if all reviewers accepts the change and the build is successful, the pull request will be merged.

In the autocomplete window, you can select to <strong>remove the feature branch</strong> and <strong>squash changes</strong> (meaning that all the checkins in the feature branch will be checked into master as one checkin)

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/branches-5.png"><img width="1024" height="533" class="alignnone size-large wp-image-4755" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/branches-5-1024x533.png" /></a>

After completion we can see the status of the pull request and the build under <strong>Pull requests</strong> -&gt; <b>completed</b>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/branches-7.png"><img width="1024" height="533" class="alignnone size-large wp-image-4765" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/branches-7-1024x533.png" /></a>

and if we look under branches, the Exclamation branch has been deleted.

In VS Code, we need to remove the orphaned Exclamation branch manually. Click the <strong>branch symbol</strong> in the lower left corner and checkout the master branch by selecting <strong>master</strong>.

Press <strong>Ctrl+Shift+P</strong> and choose <strong>Git Delete Branch…</strong> and delete the orphaned Exclamation branch.

You are now ready to create a new feature branch and work on the next set of changes.
<h1>What's next</h1>
Now we have setup branching policies and we have been through a very long and visual walkthrough of VS Code and Git, but if you are not used to Source Control and Git, it really shows how changes moves from untracked. staged, committed in your local repository to being pushed to the remote repository.

Next post is about YAML (I guess this stands for Yet Another Markup Language) and how pipelines are defined.

&nbsp;

Enjoy

<em><strong>Freddy Kristiansen</strong></em>
<span style="color: #0000ff">Technical Evangelist</span>]]></content:encoded>
    </item>
    <item>
      <title>Developing Business Central Extensions (part 3) - Build Pipeline</title>
      <link>https://docs.microsoft.com/archive/blogs/freddyk/developing-business-central-extensions-part-3</link>
      <pubDate>Mon, 12 Nov 2018 15:47:48 GMT</pubDate>
      <dc:creator><![CDATA[FreddyDK]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/freddyk/?p=4325</guid>
      <description><![CDATA[If you haven't read part 1 and part 2, you should do so before continuing here. Part 1 contains the...]]></description>
      <content:encoded><![CDATA[<span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px">If you haven't read <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-1/" rel="noopener">part 1</a> and <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-2/" rel="noopener">part 2</a>, you should do so before continuing here. <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-1/" rel="noopener">Part 1</a> contains the prerequisites and <a target="_blank" href="https://blogs.msdn.microsoft.com/freddyk/2018/11/12/developing-business-central-extensions-part-2/" rel="noopener">part 2</a> is about cloning the project and get your sandbox environment up running.</span>
<h1>Create a build agent</h1>
In order to setup continuous integration, we need a machine which can perform the actual build asks for us, the build agent.

A build agent consists of a Windows Server 2016 with some software installed: Docker, NavContainerHelper and the Azure DevOps Agent. You will also need to configure the build agent to access your organization.

I have created an ARM template, with which you can create a build agent.

The Url to this template is <a target="_blank" href="http://aka.ms/getbuildagent" rel="noopener">http://aka.ms/getbuildagent</a>

or you can create a Windows Server 2016 and install the components manually:
<ul>
 	<li>Docker EE (<a target="_blank" href="https://docs.docker.com/install/windows/docker-ee/" rel="noopener">https://docs.docker.com/install/windows/docker-ee/</a>)</li>
 	<li>NuGet Package Provider 2.8.5.201 or higher (<a target="_blank" href="https://www.nuget.org" rel="noopener">https://www.nuget.org</a>)</li>
 	<li>NavContainerHelper (<a target="_blank" href="https://www.powershellgallery.com/packages/navcontainerhelper" rel="noopener">https://www.powershellgallery.com/packages/navcontainerhelper</a>)</li>
 	<li>Azure DevOps Agent 2.141.1 installed and configured (<a target="_blank" href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/v2-windows?view=vsts" rel="noopener">https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/v2-windows?view=vsts</a>)</li>
</ul>
The script, which performs these steps for the ARM template can also be downloaded and used. You will find it here: <a target="_blank" href="https://github.com/Microsoft/nav-arm-templates/blob/master/initbuildagent.ps1" rel="noopener">https://github.com/Microsoft/nav-arm-templates/blob/master/initbuildagent.ps1</a>

In both cases you will need a <strong>Personal Access Token</strong> for your Azure DevOps Organization in order to setup a build agent.

Open your Azure DevOps organization (eg. https://dev.azure.com/&lt;organization&gt;/) and click your avatar and select Security

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/Security.png"><img class="alignnone wp-image-4345 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/Security.png" width="359" height="488" /></a>

In the Personal Access Tokens list, you can revoke tokens, regenerate them or edit scope, expiration date and more.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/PAT.png"><img class="alignnone wp-image-4355 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/PAT.png" width="1225" height="240" /></a>

Select <strong>New Token</strong>, give the token a <strong>name</strong>, an <strong>expiration date</strong>, click <strong>show all scopes</strong> to reveal <strong>Agent Pools</strong> and select <strong>Read &amp; manage</strong>.

Click <strong>Create </strong>and you will see the token one time and one time only. You will need to copy the token.

<strong>Note</strong>: You can use the same token for multiple agents.

For the automated build agent setup, open <a target="_blank" href="http://aka.ms/getbuildagent" rel="noopener">http://aka.ms/getbuildagent</a> and specify size, location, your organization and the Personal Access Token.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/getbuildagent.png"><img class="alignnone size-full wp-image-4365" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/getbuildagent.png" width="705" height="853" /></a>

After 15-20 minutes, you should see your new agent popping up (https://dev.azure.com/&lt;organization&gt;/_settings/agentpools), ready to serve you:

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/agents.png"><img class="alignnone size-full wp-image-4375" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/agents.png" width="708" height="309" /></a>
<h1>Access your secrets in the build system</h1>
In part 1, we created an Azure KeyVault, with a number of secrets (password, license file etc.). We need these secrets in the build and fortunately, Azure DevOps has a nice way of getting to those.

In <strong>Azure DevOps</strong>, under your Project, select <strong>Pipelines</strong> -&gt; <strong>Library</strong>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/Library.png"><img class="alignnone wp-image-4385 size-full" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/Library.png" width="1386" height="859" /></a>

Click + Variable group to create a variable group. Name the variable group and select Link secrets from Azure key vault as variables.

Specify the subscription (you will need to authenticate), select the key vault and add the variables

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/variables.png"><img class="alignnone size-large wp-image-4395" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/variables-1024x853.png" width="1024" height="853" /></a>

These variables will now be available for the build tasks if you link the variable group to the pipeline.
<h1>Create a build pipeline</h1>
<span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px">In </span><strong>Azure DevOps</strong><span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px">, under your Project, select </span><strong>Pipelines</strong><span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px"> -&gt; </span><strong>Builds</strong><span style="float: none;background-color: #ffffff;color: #333333;cursor: text;font-family: Georgia,'Times New Roman','Bitstream Charter',Times,serif;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;letter-spacing: normal;text-align: left;text-decoration: none;text-indent: 0px">. You will likely see that you have no build pipelines:</span>

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/no-pipe.png"><img class="alignnone size-large wp-image-4405" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/no-pipe-1024x785.png" width="1024" height="785" /></a>

Click <strong>New pipeline</strong> and click <strong>Use the visual designer</strong>. Select your repository:

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/select-repo.png"><img class="alignnone size-large wp-image-4415" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/select-repo-1024x785.png" width="1024" height="785" /></a>

Continue and select the <strong>YAML</strong> template under <strong>Configuration as code</strong>.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/config-as-code.png"><img class="alignnone size-large wp-image-4425" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/config-as-code-1024x785.png" width="1024" height="785" /></a>

Select the Default Agent pool and select the CI.yml file (included in the repository)

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/pool-and-yml.png"><img class="alignnone size-large wp-image-4435" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/pool-and-yml-1024x785.png" width="1024" height="785" /></a>

Select <strong>Variables</strong>, <strong>Variable Groups</strong>, <strong>Link variable group</strong> and <strong>Link</strong> your variable group you created above to the pipeline.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/linked.png"><img class="alignnone size-large wp-image-4445" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/linked-1024x785.png" width="1024" height="785" /></a>

Click <strong>Save &amp; queue</strong> and queue a build. You can click the link of the build to see the log...

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/building.png"><img class="alignnone size-large wp-image-4455" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/building-1024x785.png" width="1024" height="785" /></a>

<strong>Note</strong>: The first time you run a build on the agent, it will take significantly longer because it needs to pull the Business Central Docker image.

After some time, you should see your build successfully completed and one failing test.

<a href="https://msdnshared.blob.core.windows.net/media/2018/11/finish.png"><img class="alignnone size-large wp-image-4465" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/11/finish-1024x785.png" width="1024" height="785" /></a>
<h1><span>A few words about Tests</span></h1>
Update February 27th - the helloworld template repository has been updated with a new way of running tests - a special blog post on test execution will be added to this series soon.

I should mention here, that the way tests are executed in the repository while writing this blog post is still work in progress. Currently the setup just invokes a Codeunit in a container and there are multiple reasons why this isn't sufficient. We are working on making a better approach to this and I will include this in a later blog post.
<h1><span>What's next</span></h1>
<span>Now, we have our source in Azure DevOps and we have local and hosted builds running.</span>

<span>In the next Blog Post we will setup checkin policies with code reviews and pull requests - and we will create a few more pipelines for testing our app against insider builds.
</span>

&nbsp;

Enjoy

<strong><em>Freddy Kristiansen</em></strong>
<span style="color: #0000ff">Technical Evangelist</span>]]></content:encoded>
    </item>
  </channel>
</rss>