Skip to content

Image building in practice

This is the practical guide on how to build images using the avdcli. Of course, there are a million other ways to build images that would work with Schoolyear AVD. However, by sticking to this guide, you can leverage all the experience and resources of the wider Schoolyear AVD community.

Installing avdcli

To get started with this guide, you need to download the avdcli tool. You can download the latest version from here. We recommend you renaming the binary to avdcli and putting in your PATH. The rest of this guide assumes you have the avdcli command available in your terminal.

Download avdcli

Running on MacOS

The pre-built binary on the release page of avdcli is not notarized. So when you try to run it, MacOS will refuse. Follow these steps to whitelist the avdcli on your machine:

  1. Download the binary and rename it to avdcli.

  2. Allow execution of the binary by running chmod +x avdcli.

  3. Execute the downloaded avdcli from your commandline or by double clicking it. MacOS will show a popup prompting you to delete the file. Do not delete the file but dismiss the popup (“Done”).

  4. Open System Preferences and navigate to Privacy & Security.

  5. Scroll down to Security and click Open Anyway.

  6. Execute the binary from your commandline again. MacOS will show a popup, click “Open Anyway”.

Deployment repository

Many essential, pre-built scripts can be found in the Schoolyear AVD community. This guide will heavily rely on the scripts from this community as it contains all the scripts and configuration you need to build a clean base image.

We recommend you to fork this repository and check it out locally in a project folder on your local machine. The rest of this guide assumes you are working from within this directory.

To get up-and-running quickly, you can also download the repository directly from the website without using the Git toolchain. However, we do suggest making use of the Git toolchain once moving to a long-running production setting.

Schoolyear AVD community

So how is this going to work?

High-level overview of the process of making a new image available in your Schoolyear tenant:

  1. Develop layers for each application that should be part of the exam environment. For some commonly used applications there might already be a pre-built layer available.

  2. Combine the desired layers in an Image Package using avdcli image package.

  3. Build the exam image using avdcli package deploy.

  4. Create a new deployment template in Azure.

  5. Publish a new Secure App in your Schoolyear tenant.

  6. Test if the new App works before use in an exam.

Layers?

We are going to package each application we want to use in the image in what we call a layer. Then we use avdcli to combine these layers and build an image out of them.

The method to image building described in this guide focuses on building “exam-ready” images. We want the images used in exams to be static and fast to start up. Furthermore, we want to limit the reliance on network services, so we can isolate the exam network as much as possible. This means baking all the software and its configuration in the image itself and not relying on dynamic packaging tools.

To give an example, for one exam we want to set up an exam environment with both Office 365 and R-studio. You would create a layer for Office and a layer for R-Studio. Then you would use avdcli to combine these layers and build the image for the exam.

Implementing a new layer

You can develop a layer for each application you want to package into an image. These layers should be developed to work independently so they can be reused in multiple images. For example, if you want to build an image with Office and R-Studio, you should make a layer for each of those applications. Those two layers should be developed in such a way that you can then later reuse the R-Studio layer to build an image with R-Studio and Python for example.

You can follow these steps to implement your own layer. The R-studio image is used as an example.

1. Create a new layer folder

Navigate to your avd-deployments folder you downloaded in a previous step and create a new image layer:

Create a new image layer
avdcli image new -n rstudio -o ./images/rstudio

This will create a new folder rstudio in the images directory. To conform to the existing folder structure of the avd-deployment project, we recommend you to put your image layers in this folder.

The rest of these steps are performed in this ./images/rstudio folder:

Navigate to the image layer
cd ./images/rstudio

2. Configure build steps

Now that we have a new image layer to work on, we’ll start by implementing the build steps. These are the steps that are executed during the image building process before the image snapshot is created. This would be the stage in which you want to run software installers and alike.

Avdcli is built on top of Azure Image Builder. Azure Image Builder works by configuring a list of steps that should be executed on a VM before an image snapshot is created. Azure Image Builder will then execute these steps automatically. You can configure these steps in the build_steps.json5 file in the root directory of the image layer.

The idea is that you put the scripts you need for image building in the resources folder, and then configure these scripts to be executed in the build_steps.json5 file. To make sure your resources do not conflict with those of other layers, the convention is to create a subfolder in the resources directory with the name of the layer. If your layer called “rstudio” (./images/rstudio), you would put your resources in the ./images/rstudio/resources/rstudio folder.

The build_steps.json5 file is divided into three sections to give you some control over the order of execution when combined with other layers. Each build step has a name and a type property and possibly some extra properties specific to the type. The schema for these build steps is described in the Azure Image Builder documentation.

build_steps.json5
{
// if you need to execute steps before any "default" steps are executed
pre: [],
// the default place to put your building steps
default: [
{
name: "Run Example",
type: "PowerShell",
inline: [
"C:\\imagebuild_resources\\example\\example.ps1"
],
runAsSystem: true,
runElevated: true
},
{
"name": "Windows Reboot",
"type": "WindowsRestart"
}
],
// if you need to execute steps after all "default" steps are executed
post: []
}

Be wary of using the “Window Reboot” and “Windows Update” build steps, as they can easily add 10-20 minutes to the image building process, and most installations do not need it.

Installing an MSI/EXE

Most software you may want to package can be installed using an .msi or .exe installer. This section gives you a starter template to get going.

First, create the installation script in a subfolder of the resources directory. For example, if your layer is called rstudio, the file path would be images/rstudio/resources/install.ps1.

images/rstudio/resources/rstudio/install.ps1
param (
[string]$msiUrl
[string]$name
[string]$msiPath
[string]$msiArguments
)
$ErrorActionPreference = "Stop"
function Msi-Download-Install {
param (
[string]$MsiUrl,
[string]$Name,
[string]$LocalFilePath,
[string]$MsiArguments
)
Write-Host "Downloading $Name MSI"
Invoke-WebRequest $MsiUrl -OutFile $LocalFilePath
Write-Host "Download of $Name completed"
Write-Host "Installing $Name"
$msiExecArguments = "/i `"$LocalFilePath`" /q /l*! output.log"
if (-not [string]::IsNullOrEmpty($MsiArguments)) {
$msiExecArguments += " " + $MsiArguments
}
$main_process = Start-Process msiexec.exe -ArgumentList $msiExecArguments -NoNewWindow -PassThru
$log_process = Start-Process "powershell" "Get-Content -Path output.log -Wait" -NoNewWindow -PassThru
$main_process.WaitForExit()
$log_process.Kill()
Write-Host "Installation of $Name complete"
Write-Host "Removing $Name MSI"
Remove-Item "$LocalFilePath"
Write-Host "Removing MSI installation log"
Remove-Item "output.log"
return $main_process.ExitCode
}
$msiOut = Msi-Download-Install -MsiUrl $msiUrl -Name $name -LocalFilePath "C:\rstudio.msi" -MsiArguments $msiArguments
if ($msiOut -ne 0)
{
Write-Host "Exiting after installing $name, because exit code is not 0, but $msiOut"
exit $msiOut
}
Write-Host "$name installations complete"

Once you create the installation script, you can reference it in the build steps:

images/rstudio/build_steps.json5
{
default: [
{
name: "Install R-Studio",
type: "PowerShell",
inline: [
"C:\\imagebuild_resources\\rstudio\\install.ps1 \"https://example.com/rstudio.msi\" \"R-Studio\" \"C:\\rstudio.msi\""
],
runAsSystem: true,
runElevated: true
},
],
}

3. Network configuration

For most applications, you can skip this step. However, some applications require an HTTP(S) connection to a publicly hosted service. The most common use-case for this is public software-activation servers.

You can add a list of whitelisted host:port combinations in the layer’s properties.json file. You can use the * symbol as a wildcard.

Note that this only works for HTTP(S) services, not for general TCP/UDP connections. You can read more about how the whitelisting of these connections works here.

properties.json5
{
whitelistedHosts: {
// Use this property to whitelist HTTP(S) connections that 3rd-party application may need to function
// This is commonly used for internet-facing license servers
//
// The key should have the structure of "host:port" and may include wildcards ("*")
// The value is ignored
"catalogartifact.azureedge.net:443": {},
"*.hip.live.com:443": {},
},
}

4. Setup scripts

The build scripts and steps you added in the previous step will be executed during the image build process. They will be executed once after which a snapshot of the image is taken. This image will then be used to deploy AVD sessionhosts.

Some layers may also need to execute scripts on the sessionhosts themselves, after they are deployed. For example, to configure firewall rules that should take effect after the sessionhost is properly deployed.

For this use-case, some special folders in the resources directory can be used. By putting scripts in these three folders, they will be automatically executed at specific times.

The following triggers are supported:

  • Sessionhost setup scripts: Executed during the deployment of each sessionhost.
  • Session scripts: Executed when a student logs in. Executed as the SYSTEM user.
  • User scripts: Executed when a student logs in. Executed as the student’s user.

For more information about these folders and which parameters are passed to the scripts, you can read more here.

There are two steps to make this work:

  1. Put the scripts you want in their respective folders. Make sure you give them a name in the format 000(_pre/_post)_name.ps1. By including _pre or _post in their name, you can control the order in which they are executed relative to the other layers. You can adjust their order within the layer, using the three digits at the start of the filename.

  2. Include the default_layers/scripts_setup layer when running avdcli image package. This step will be discussed in the Image Packaging section of this page.

5. Test each layer individually

Before you start building images in Azure, you should test each layer you’ve developed in isolation. You can do this easily on a (local) VM or a clean Windows machine. Make sure all the scripts work, before you move on. Debugging scripts in isolation on a VM you can interact with is much easier than debugging a problem when using the Azure Image Builder.

Create an Image Package

Once you’ve developed all the layers and tested them in isolation, you can move on to building the image in Azure.

The first step to this is to create an “Image Package”. This is a folder, usually called ./out, that contains all the scripts, resources and configurations of all the layers you want to include in your image.

You create an Image Package by running the following command:

avdcli image package
avdcli image package \
-l default_layers/common_config \
-l default_layers/clean \
-l default_layers/vdot \
-l default_layers/windows_update \
-l default_layers/vdi_browser \
-l default_layers/scripts_setup \
-l default_layers/network_lockdown \
-l images/office365 \
-l images/rstudio \
--overwrite

In this example we package all the commonly used default_layers and the office365 and rstudio images into one Image Package. This package will be written to the ./out folder by default unless you specify another path using the -o flag.

If you want to include even more layers, you can do so by specifying the -l flag multiple times.

Image Package deployment

In the previous step, you created an Image Package. Now, we’ll deploy it to Azure and start the image building process.

Substitute the highlighted parameters with your own and run the command:

avdcli package deploy
avdcli package deploy \
-n office365 \
-s subscriptionid \
-rg imagebuilding \
-r "https://imageresources.blob.core.windows.net/resources" \
--start

This guide assumes you already went through the Quick-Start guide and set up all the required resources. If not, please follow the guide on this page.

Debugging

You won’t be the first person for which the first image build does not work on the first try. It rarely does. Developing layers and building images involves debugging. Debugging errors in a fully automated process is harder than debugging interactively on a (local) VM. That’s why we recommend you to test your scripts as much as possible before you run avdcli package deploy.

However, you will likely have to debug in the Azure Image Builder process as well.

You can follow these steps during your debug iterations:

  1. Navigate to AVD | Custom image templates in the Azure Portal.
  2. Find the name of the image template that failed and click on its staging resource group.
  3. If the image build already failed, you will find a storage account in this resource group that holds log files. If the image build is still ongoing, there will be a container resource. If you navigate to this container resource you will be able to see the live logs of the container that is executing the build steps.

Testing the image

Did you Image Builder finish successfully? Congrats. Now you can test it in a real Schoolyear exam.

  • Does the image work as expected when you use it as a student?
  • Does the exam environment meet the requirements of the educators?
  • Are students blocked from accessing non-permitted resources?

To test the image in a Schoolyear, follow the steps in the Quick-Start guide to make the image available in your Schoolyear tenant, plan an exam with that image and try it out yourself first.