Citrix App Layering Series Part 2: Recipe for App Layering 2211 in Azure & Review of Azure Connector Architectural Changes from App Layering 2211+

Authors: Gavin Strong, David Pisa, Wendy Gay

The purpose of this blog is to allow Citrix Administrators to Configure Citrix App Layering 2211. In the previous part of this series we focused on the implementation of the ELM Appliance and configuration of the App Layering Connectors in Azure. However, in recent releases of App Layering 2211, there has been architectural changes and most notably a complete new method for deploying the App Layering Azure Deployments Connector.

In the Second part of the App Layering Series, we will focus on the following:

  • ELM Instance deployment Steps with App Layering 2211
  • ELM Appliance upgrade steps from App Layering 2208 to 2211.
  • Configuration of the App Layering Management Console.
  • Configuration of a network file share
  • Setup of Directory Junction
  • Configuration of Azure Managed Identity (Required for Azure Connectors Deployment)
  • Configuration of Azure Deployments Connector
  • Creation of Azure Compute Galleries
  • Deployment of ARM Templates for the Purposes of App Layering
  • OS Layer Creation
  • Platform Layer Creation
  • App Layer Creation
  • Layered Image Template Deployment.

What’s new in App Layering 2211:

  • New Azure Deployments connectors. Two new Azure connectors have been created and implemented into App Layering: Azure Deployments and Machine Creation for Azure Deployments. Both connectors also support Government environments (Azure Government and Machine Creation for Azure Government, respectively). The old Azure connector and Machine Creation for Azure connector (as well as their Government counterparts) are now deprecated, but still available for use for a limited time. For more information, see Azure deployments.

New Installation: Deployment of ELM Appliance for App Layering 2211

Download App Layering 2211 from the following the Citrix Downloads Site:  https://www.citrix.com/downloads/citrix-virtual-apps-and-desktops/product-software/citrix-virtual-apps-and-desktops-alleditions-2212.html

Download File to the server where you plan to deploy ELM appliance from via PowerShell:

Once downloaded, extract the contents of the Compressed Folder, this can take some time so feel free to grab a coffee at this point.

Once the App Layering files have been downloaded and Extracted successfully, open PowerShell (as Administrator) and install the AZ Module.

  • Install-module Az.Accounts -Force
  • Install-module Az
  • Import-module Az -Force

The below steps can be performed manually, however they are not required as our next step of running the App Layering ELM Deployment Script will include these steps.

  • Connect-AzAccount
  • Get-azsubscription
  • Set-AzContext <SubscriptionName>

Note: Installing the AZ Modules can take some time:

In PowerShell – Navigate to the Folder where the AzureELMDeploymentV7.ps1 script resides.

Within this location, run: “.\AzureELMDeploymentV7.ps1”

  • This will start the script, at this point, select the option “R” to run once.
  • Set Deployment Name – This can be called whatever your preference.
  • Select the Storage Type to use – We select default “Storage Type”, click enter.
  • Select AzureCloud for the Azure Environment to deploy in.

We will be prompted to Login to Azure Active Directory to Authenticate and enumerate the Azure Subscriptions we are a member of:

After successfully authenticating, we are provided with a list of Azure Subscriptions we are a member of. At this Point, we are prompted to enter the Subscription Name where to deploy our ELM Appliance in.

In this example, we selected the subscription “sls-lab-shared”

Next, we are prompted to select which Azure Resource Group to deploy the ELM Appliance.

The next step we need to specify the Virtual network to use, and then select the Subnet we will use:

You will then be required to specify the Virtual Machine Size – For this example we proceeded with the Default Virtual Machine Size.

Next, specify a Username and Password for accessing the ELM Appliance.

Script Completes and begins to upload the ELM Appliance VHD to Azure Resource Group specified earlier.

Uploading of the VHD to Azure has been successful:

  • You will see the result “copy blob. 100% complete”

Configuration of the App Layering Management Console:

In our previous task, we deployed the ELM Appliance via PowerShell Successfully.

Now that we have successfully provisioned the ELM Instance in Azure, we can now access the App Layering Management Console (ALMC) via the Browser. The ALMC will be accessed via the IP Address of the ELM Appliance which we deployed during our previous task.

For the first logon to the App Layering, you must login with the root administrator account.

The Default credentials for the root administrator account are:

Username: Administrator

Password: Unidesk1

Upon Logon, the first page we land on is the “Getting Started” page, which lists a number of pre-requisites before we can begin provisioning layers, the pre-requisites are:

  1. Configure a Network File Share
  2. Add a Connector Configuration
  3. Create an OS Layer
  4. Create Platform and App Layers
  1. Creating Network File Share:

Pre-requisites for configuring file share: https://docs.citrix.com/en-us/citrix-app-layering/4/configure/set-up-file-share.html

Requirements

When setting up the appliance’s file share:

  • The file share must be configured using Server Message Block (SMB) technology.
  • The Service account that the App Layering appliance uses to connect to the file share must have full permissions for that file share.
  • Users require read-only access to the file share. If you plan to enable user layers on the images you publish, also set the file share permissions detailed in Configure security on user layer folders.
  • Ensure that you have the minimum storage space requirement of 40-100 GB for your file share. Note: Storage space is expandable. You can add space to a disk, or add other disks to the appliance.

Create the network file share

Configure a file share that uses the SMB protocol.

  • Follow the vendor’s instructions for setting up a file share using the SMB protocol.

Configure the App Layering appliance to access the file share

Once you have created a file share, configure the App Layering appliance to attach to it. You can configure the appliance via the App Layering management console.

  1. In the Management Console, select System > Network File Shares and click Edit.(see image below)
  2. Specify an SMB file share path, User name, and Password for the file share.
  3. Click Confirm and Complete to see if you can connect to the file share. The File Share is saved if the connection succeeds, or displays an error if the connection fails.

Direct upgrade of App Layering from 2208 to 2211:

If upgrading from App Layering 2208 to 2211, it is possible to upgrade the ELM Appliance Directly by using the Appliance Upgrade Package.

If using an older version of App Layering (prior to 2208) – See the following documentation to understand your supported upgrade path. – Before you upgrade.

  • Download the Upgrade package from Citrix Downloads site – the file is called citrix_app_layering_upgrade_pkg_22.11.0.vhd.
  • Copy the Upgrade VHD File into network file share:
  • Once you have copied it to the network SMB Share, open the ALMC and in the right corner, select “Upgrade Appliance“:

It is best practice to take a backup / snapshot of the ELM Appliance in Azure before proceeding with the upgrade process:

Next, Confirm Upgrade:

Next, wait for the App Layering Appliance to upgrade (Do not refresh the page!):

Once the Upgrade process has complete, you will see the Notification “App Layering Appliance Upgrade has finished”. – Select “Return to Login”

Azure Deployments Connectors – Step by Step Configuration

In Part 1 of our App Layering Series, we took you through the Configuration details of the older Azure Connector which is currently in the process of being deprecated. See Part 1 of the App Layering Series here.

Over the previous 2 Tasks, we provided details for both deploying a new ELM Appliance, and the steps required to upgrade the ELM Appliance from an older version of App Layering to version 2211.

From App Layering 2211 onwards, the architecture for the Azure Deployments Connector has changed significantly. The new Azure Deployments connector configuration refers to the creation of Azure Deployments using Azure Resource Manager (ARM) templates. ARM Templates are Azure-specific JSON documents that define infrastructure and configuration as code – IaaS(Infrastructure as Code).

New Azure Deployments connectors. Two new Azure connectors have been created and implemented into App Layering: Azure Deployments and Machine Creation for Azure Deployments. Both connectors also support Government environments (Azure Government and Machine Creation for Azure Government, respectively). The old Azure connector and Machine Creation for Azure connector (as well as their Government counterparts) are now deprecated, but still available for use for a limited time. For more information, see Azure deployments.” – Ref: App Layering what’s New in 2211

All Azure resources created by the App Layering Azure Deployments connector are created using the deployment of a user specified ARM template. These templates allow an administrator to extensively customize what resources are created and how they’re configured.

Within this task, we will perform the following:

  • Configuration of the Azure Deployments Connector for App Layering 2212.
  • Configuration of Azure Managed Identities User-Assigned and System-Assigned Managed Identities
  • App Layering Deployment Types– Machine, Cache Disk, Layered Image, Boot Image
  • Configuration of ARM Templates and Azure Template Specs
  • Create and import App Layering Deployment Type JSON files into Azure Template Specs.
  • Configuration of an Azure Compute Gallery
  • Configuration of Custom Data required for Azure Deployments Connector Configuration

Configuring Azure Deployments Connector in the App Layering Management Console:

Within the App Layering Management Console, navigate to the “Connectors” page > Add Connector Configuration:

Select Azure Deployments / Machine Creation for Azure Deployments.

Select “New” to begin deployment of new Azure Deployments Connector.

This will open the page to “Create Azure Deployments Configuration” window. Firstly, Name the Connector Configuration.

In order to proceed at this point, we need to ensure that the ELM Appliance has access and visibility to the resource group. The next step is to configure a Managed Identity on the ELM Appliance in Azure.

Azure Managed Identity Requirement for Azure Deployments Connector:

From App Layering 2211 onwards – The ELM Appliance in Azure must have a Managed Identity associated with it – as per Citrix Documentation: https://docs.citrix.com/en-us/citrix-app-layering/4/connect/azure-deployments.html#app-layering-appliance-machine-identity

  • The managed Identity is required in order to configure the Azure Deployments Connector and it replaces the requirement of configuring an App Registration/Service Principal in previous versions of App Layering, so let’s create the Managed Identity configuration in Azure.  This will allow access to the Resource Group in Azure directly from the App Layering Management Console (ALMC).

For the Managed Identity – You can use either a user assigned Managed Identity, or a System Assigned Managed Identity. Below, we will detail the steps for both scenarios.

Creating a User assigned managed Identity:

For this subtask, we will focus on creating a User-Assigned Managed Identity in Azure with Contributor role at the Resource Group Level, and then assign the identity to the ELM Appliance. Go to Managed Identities in Azure:

Enter details for the User Assigned Managed Identity:

  • Subscription
  • Resource Group
  • Region
  • Managed Identity Name

User-Assigned Managed Identity has been created Successfully:

Next, you can add role assignments to the Managed Identity. It is recommended that the role assignment has contributor role at the Resource Group Level.

Next, navigate to the ELM Appliance VM on Azure:

Select on “Identity“:

Select on User Assigned Tab and Add user assigned Managed Identity.

Click “Add” User Assigned Managed Identity:

Confirm the User-Assigned Managed Identity has been applied to the ELM Appliance.

 Creating a System assigned managed Identity:

For this subtask, we will create a system-assigned managed Identity on the ELM Appliance. You can also use a system assigned Managed Identity for the ELM Appliance. To Configure the system-assigned managed Identity, locate the ELM Appliance in Azure > Go To Identities > System Assigned.

Under Status, toggle the System Assigned Identity to “On”.

  • Select Azure Role Assignments
  • Select Resource Group and “Contributor” role and save changes.

**The Managed Identity removes the requirement that we previously had in the legacy Azure Connector configuration steps(we no longer need App Registration/Service Principal), Once we have the Managed Identity with Contributor role at the Resource Group or Subscription Level, it enables the App Layering Management Console/ELM Appliance to access the Azure Resource Group that it resides.**

As you can see, we can now browse Azure Resource Group via the App Layering Management Console – Connector configuration view:

  • However, we currently do not have any Azure Template Specs existing in our Resource Group where the ELM Appliance resides, so that is our next step!

Import Azure Template Specs into MS Azure:

Next, we want to create our Azure Deployments Connector configuration – 1st, what we need to do is deploy our Azure Template specs for each of the deployment types:

  • Machine
  • Cache Disk
  • Layered Image
  • Boot Image

There are 4 Deployment Types that are applicable for the Azure Deployments Connector Configuration. For the purpose of this blog we will import all 4 deployment templates into Azure so we can proceed with configuration of the App Layering Connector.

Deployment types

There are four deployment types, each requiring their own template spec. The deployment types differ in the type of resources they create, the inputs they receive, and the outputs they produce to override default behavior. For more information on these concepts, see Authoring ARM templates.

Machine Deployment Type:

The Machine deployment creates a virtual machine (VM). VMs created by Machine deployments can composite layered images and package layers. If the optional Layered Image deployment isn’t specified, then a VM is the final result of publishing an image. In which case, the VM can be used as-is or as a Machine Creation Services (MCS) master image.

If a Layered Image deployment is specified, then the resources created by the Machine deployment are deleted after the Layered Image deployment completes. Otherwise, App Layering does not delete the resources (unless the deployment fails).

Cache disk Deployment Type:

The Cache Disk deployment creates an Azure managed disk. This disk is used to contain the Compositing Engine boot image. The App Layering appliance uploads the contents to the disk after it’s created.

If a Boot Image deployment is specified, then the resources created by the Cache Disk deployment are deleted after the Boot Image deployment completes. Otherwise, App Layering deletes the resources during cache cleanup.

Layered image (optional) Deployment Type:

The Layered Image deployment is an optional deployment type. The resulting resources are the final result of publishing a layered image. No particular resource type is required to be created. The Layered Image deployment can be used to produce a compute gallery image, a managed disk, or any other type of resource.

Note: App Layering doesn’t delete the resources created by the layered image deployment (unless the deployment fails).

Boot image (optional) Deployment Type:

The Boot Image deployment is an optional deployment type. The resulting resources are used to create the OS disks of the VMs created by Machine deployments. It isn’t required to create any particular type of resource, however it must create a resource that can be used to create an OS disk for a VM. This deployment can be used to produce a compute gallery image, or any other type of resource that can be used as the source of a disk.

App Layering deletes the resources created by the boot image deployment during cache cleanup.

Reference <https://docs.citrix.com/en-us/citrix-app-layering/4/connect/azure-deployments.html>

Import Starter Templates for each Deployment Option in Azure Template Specs

As per the following documentation, we need to create Azure Template specs using the starter templates associated with each deployment type detailed in the following Citrix Documentation “Starter Templates”. Once the Template Specs have been created and imported to Azure, we can proceed with the Azure Deployments Connector Configuration in the ALMC.  There is an example Starter Template associated with each of the Deployment Options above, and they can be used as-is detailed in the referenced documentation “Starter Templates”.  

If you are unfamiliar with Azure and ARM Templates, don’t worry – I hope this blog will help better understand why and how we use ARM Templates.

Azure Template Specs are a type of Azure resource that stores and versions an ARM Template for later us in an ARM Template Deployment.

In relation to the 4 deployment types referenced above – You must specify between 2 to 4 template specs for each Azure Deployments connector configuration. Each Deployment type requires a corresponding version of a template spec.

The Cache Disk and Machine Deployments are required for all Azure Deployment Connector configurations, however the Boot Image and Layered Image Deployments are optional.

Reference Documentation for info on Azure Template Specs.

Firstly, we create our JSON Files for each of the 4 deployment types. In this example, I have used Visual Studio to create the JSON Templates, I also installed the ARM Template plugin for Visual Studio to highlight any errors in the code if there is any. (This plugin will be important if you are creating advanced, Custom ARM Templates.)

Below, is the starter templates for each Deployment type, each template can be used as-is and imported directly into Azure under “Azure Template Specs”.

CacheDisk.JSON Template:

{

    “$schema”: “https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#“,

    “contentVersion”: “1.0.0.0”,

    “parameters”: {

        “al”: {

            “type”: “object”

        }

    },

    “variables”: {

        “custom”: “[parameters(‘al’).context.config.custom]”,

        “location”: “[if(contains(variables(‘custom’), ‘location’), variables(‘custom’).location, resourceGroup().location)]”,

        “name”: “[concat(parameters(‘al’).context.item.name,’-‘, parameters(‘al’).context.item.id)]”,

        “tags”: {

            “alTaskId”: “[parameters(‘al’).context.taskId]”,

            “alUser”: “[parameters(‘al’).context.user]”,

            “alComment”: “[parameters(‘al’).context.comment]”,

            “alItemType”: “[parameters(‘al’).context.item.type]”,

            “alItemId”: “[parameters(‘al’).context.item.id]”,

            “alItemName”: “[parameters(‘al’).context.item.name]”,

            “alItemVersion”: “[parameters(‘al’).context.item.version.name]”,

            “alConfigId”: “[parameters(‘al’).context.config.id]”,

            “alConfigName”: “[parameters(‘al’).context.config.name]”

        },

        “hasDiskAccess”: “[contains(variables(‘custom’), ‘diskAccessId’)]”,

        “storageSku”: “[if(contains(variables(‘custom’), ‘storageSku’), variables(‘custom’).storageSku, ‘StandardSSD_LRS’)]”

    },

    “resources”: [

        {

            “type”: “Microsoft.Compute/disks”,

            “apiVersion”: “2021-08-01”,

            “name”: “[variables(‘name’)]”,

            “location”: “[variables(‘location’)]”,

            “tags”: “[variables(‘tags’)]”,

            “sku”: {

                “name”: “[variables(‘storageSku’)]”

            },

            “properties”: {

                “creationData”: {

                    “createOption”: “Upload”,

                    “uploadSizeBytes”: “[parameters(‘al’).input.uploadSize]”

                },

                “incremental”: “false”,

                “diskAccessId”: “[if(variables(‘hasDiskAccess’), variables(‘custom’).diskAccessId, null())]”,

                “networkAccessPolicy”: “[if(variables(‘hasDiskAccess’), ‘AllowPrivate’, ‘AllowAll’)]”,

                “publicNetworkAccess”: “[if(variables(‘hasDiskAccess’), ‘Disabled’, ‘Enabled’)]”

            }

        }

    ]

}

MachineTemplate.JSON Template

{

    “$schema”: “https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#“,

    “contentVersion”: “1.0.0.0”,

    “parameters”: {

        “al”: {

            “type”: “object”

        }

    },

    “variables”: {

        “custom”: “[parameters(‘al’).context.config.custom]”,

        “location”: “[if(contains(variables(‘custom’), ‘location’), variables(‘custom’).location, resourceGroup().location)]”,

        “name”: “[concat(‘al-‘, parameters(‘al’).context.taskId)]”,

        “vmName”: “[concat(variables(‘name’), ‘-vm’)]”,

        “nicName”: “[concat(variables(‘name’), ‘-nic’)]”,

        “diskName”: “[concat(variables(‘name’), ‘-disk’)]”,

        “diskId”: “[resourceId(‘Microsoft.Compute/disks’, variables(‘diskName’))]”,

        “tags”: {

            “alTaskId”: “[parameters(‘al’).context.taskId]”,

            “alUser”: “[parameters(‘al’).context.user]”,

            “alComment”: “[parameters(‘al’).context.comment]”,

            “alItemType”: “[parameters(‘al’).context.item.type]”,

            “alItemId”: “[parameters(‘al’).context.item.id]”,

            “alItemName”: “[parameters(‘al’).context.item.name]”,

            “alItemVersion”: “[parameters(‘al’).context.item.version.name]”,

            “alConfigId”: “[parameters(‘al’).context.config.id]”,

            “alConfigName”: “[parameters(‘al’).context.config.name]”

        },

        “source”: “[parameters(‘al’).input.disk.image]”,

        “isFromImage”: “[not(contains(variables(‘source’), ‘diskId’))]”,

        “generation”: “[if(contains(variables(‘custom’), ‘generation’), variables(‘custom’).generation, ‘V2’)]”,

        “vmSize”: “[if(contains(variables(‘custom’), ‘vmSize’), variables(‘custom’).vmSize, ‘Standard_D2s_v3’)]”,

        “storageSku”: “[if(contains(variables(‘custom’), ‘storageSku’), variables(‘custom’).storageSku, ‘StandardSSD_LRS’)]”

    },

    “resources”: [

        {

            “type”: “Microsoft.Network/networkInterfaces”,

            “apiVersion”: “2020-11-01”,

            “name”: “[variables(‘nicName’)]”,

            “location”: “[variables(‘location’)]”,

            “tags”: “[variables(‘tags’)]”,

            “properties”: {

                “ipConfigurations”: [

                    {

                        “name”: “ipconfig1”,

                        “properties”: {

                            “privateIPAllocationMethod”: “Dynamic”,

                            “subnet”: {

                                “id”: “[variables(‘custom’).subnetId]”

                            },

                            “primary”: true,

                            “privateIPAddressVersion”: “IPv4”

                        }

                    }

                ],

                “dnsSettings”: {

                    “dnsServers”: []

                },

                “enableAcceleratedNetworking”: false,

                “enableIPForwarding”: false

            }

        },

        {

            “condition”: “[not(variables(‘isFromImage’))]”,

            “type”: “Microsoft.Compute/disks”,

            “apiVersion”: “2021-08-01”,

            “name”: “[variables(‘diskName’)]”,

            “location”: “[variables(‘location’)]”,

            “tags”: “[variables(‘tags’)]”,

            “sku”: {

                “name”: “[variables(‘storageSku’)]”

            },

            “properties”: {

                “osType”: “Windows”,

                “hyperVGeneration”: “[variables(‘generation’)]”,

                “creationData”: {

                    “createOption”: “Copy”,

                    “sourceResourceId”: “[variables(‘source’).diskId]”

                },

                “diskSizeGB”: “[parameters(‘al’).input.disk.size]”,

                “networkAccessPolicy”: “DenyAll”,

                “publicNetworkAccess”: “Disabled”

            }

        },

        {

            “type”: “Microsoft.Compute/virtualMachines”,

            “apiVersion”: “2021-07-01”,

            “name”: “[variables(‘vmName’)]”,

            “location”: “[variables(‘location’)]”,

            “dependsOn”: [

                “[resourceId(‘Microsoft.Network/networkInterfaces’, variables(‘nicName’))]”,

                “[variables(‘diskId’)]”

            ],

            “tags”: “[variables(‘tags’)]”,

            “properties”: {

                “hardwareProfile”: {

                    “vmSize”: “[variables(‘vmSize’)]”

                },

                “storageProfile”: {

                    “imageReference”: “[if(variables(‘isFromImage’), createObject(‘id’, variables(‘source’).id), null())]”,

                    “osDisk”: {

                        “osType”: “Windows”,

                        “createOption”: “[if(variables(‘isFromImage’), ‘FromImage’, ‘Attach’)]”,

                        “caching”: “ReadWrite”,

                        “deleteOption”: “Delete”,

                        “diskSizeGB”: “[parameters(‘al’).input.disk.size]”,

                        “managedDisk”: “[if(variables(‘isFromImage’), createObject(‘storageAccountType’, variables(‘storageSku’)), createObject(‘id’, variables(‘diskId’)))]”

                    },

                    “dataDisks”: []

                },

                “networkProfile”: {

                    “networkInterfaces”: [

                        {

                            “id”: “[resourceId(‘Microsoft.Network/networkInterfaces’, variables(‘nicName’))]”

                        }

                    ]

                },

                “diagnosticsProfile”: {

                    “bootDiagnostics”: {

                        “enabled”: true

                    }

                },

                “licenseType”: “[if(contains(variables(‘custom’), ‘licenseType’), variables(‘custom’).licenseType, null())]”,

                “userData”: “[parameters(‘al’).input.vm.userData]”

            }

        }

    ],

    “outputs”: {

        “diskId”: {

            “type”: “string”,

            “value”: “[reference(variables(‘vmName’)).storageProfile.osDisk.managedDisk.id]”

        },

        “message”: {

            “type”: “string”,

            “value”: “[format(‘See [link=\”{0}/#@{1}/resource/{2}\”]{2}[/link].’, environment().portal, tenant().tenantId, resourceId(‘Microsoft.Compute/virtualMachines’, variables(‘vmName’)))]”

        }

    }

}

LayeredImage.JSON Template

{

    “$schema”: “https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#“,

    “contentVersion”: “1.0.0.0”,

    “parameters”: {

        “al”: {

            “type”: “object”

        }

    },

    “variables”: {

        “invalidChars”: [ ” “, “(“, “)”, “[[“, “]”, “{“, “}”, “!”, “@”, “#”, “$”, “%”, “^”, “&”, “*”, “+”, “/”, “\\”, “‘”, “\””, “|”, “`”, “~”, “<“, “>”, “,”, “?”, “*” ],

        “numericChars”: [ “0”, “1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9” ],

        “custom”: “[parameters(‘al’).context.config.custom]”,

        “location”: “[if(contains(variables(‘custom’), ‘location’), variables(‘custom’).location, resourceGroup().location)]”,

        “name”: “[join(split(parameters(‘al’).context.item.name, variables(‘invalidChars’)), ‘_’)]”,

        “tags”: {

            “alTaskId”: “[parameters(‘al’).context.taskId]”,

            “alUser”: “[parameters(‘al’).context.user]”,

            “alComment”: “[parameters(‘al’).context.comment]”,

            “alItemType”: “[parameters(‘al’).context.item.type]”,

            “alItemId”: “[parameters(‘al’).context.item.id]”,

            “alItemName”: “[parameters(‘al’).context.item.name]”,

            “alItemVersion”: “[parameters(‘al’).context.item.version.name]”,

            “alConfigId”: “[parameters(‘al’).context.config.id]”,

            “alConfigName”: “[parameters(‘al’).context.config.name]”

        },

        “splitVer”: “[split(parameters(‘al’).input.diskName, ‘.’)]”,

        “major”: “[if(equals(0, length(join(split(variables(‘splitVer’)[0], variables(‘numericChars’)), ”))), variables(‘splitVer’)[0], ‘1’)]”,

        “minor”: “[if(greater(length(variables(‘splitVer’)), 1), if(equals(0, length(join(split(variables(‘splitVer’)[1], variables(‘numericChars’)), ”))), variables(‘splitVer’)[1], ‘0’), ‘0’)]”,

        “version”: “[format(‘{0}.{1}.{2}’, variables(‘major’), variables(‘minor’), parameters(‘al’).context.item.version.number)]”,

        “galleryName”: “[variables(‘custom’).gallery]”,

        “generation”: “[if(contains(variables(‘custom’), ‘generation’), variables(‘custom’).generation, ‘V2’)]”

    },

    “resources”: [

        {

            “type”: “Microsoft.Compute/galleries/images”,

            “name”: “[format(‘{0}/{1}’, variables(‘galleryName’), variables(‘name’))]”,

            “apiVersion”: “2021-07-01”,

            “location”: “[variables(‘location’)]”,

            “properties”: {

                “description”: “[parameters(‘al’).context.item.description]”,

                “hyperVGeneration”: “[variables(‘generation’)]”,

                “osType”: “Windows”,

                “osState”: “Specialized”,

                “endOfLifeDate”: “2030-01-01T00:00:00Z”,

                “identifier”: {

                    “publisher”: “AppLayering”,

                    “offer”: “[variables(‘name’)]”,

                    “sku”: “[variables(‘generation’)]”

                }

            },

            “tags”: “[variables(‘tags’)]”,

            “resources”: [

                {

                    “type”: “versions”,

                    “apiVersion”: “2020-09-30”,

                    “name”: “[variables(‘version’)]”,

                    “location”: “[variables(‘location’)]”,

                    “dependsOn”: [

                        “[resourceId(‘Microsoft.Compute/galleries/images’, variables(‘galleryName’), variables(‘name’))]”

                    ],

                    “tags”: “[variables(‘tags’)]”,

                    “properties”: {

                        “publishingProfile”: {

                            “replicaCount”: 1,

                            “targetRegions”: [

                                {

                                    “name”: “[variables(‘location’)]”

                                }

                            ]

                        },

                        “storageProfile”: {

                            “osDiskImage”: {

                                “source”: {

                                    “id”: “[parameters(‘al’).input.source.diskId]”

                                }

                            }

                        }

                    }

                }

            ]

        }

    ],

    “outputs”: {

        “message”: {

            “type”: “string”,

            “value”: “[format(‘See [link=\”{0}/#@{1}/resource/{2}\”]{2}[/link].’, environment().portal, tenant().tenantId, resourceId(‘Microsoft.Compute/galleries/images/versions’, variables(‘galleryName’), variables(‘name’), variables(‘version’)))]”

        }

    }

}

BootImage.JSON Template:

{

    “$schema”: “https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#“,

    “contentVersion”: “1.0.0.0”,

    “parameters”: {

        “al”: {

            “type”: “object”

        }

    },

    “variables”: {

        “custom”: “[parameters(‘al’).context.config.custom]”,

        “location”: “[if(contains(variables(‘custom’), ‘location’), variables(‘custom’).location, resourceGroup().location)]”,

        “tags”: {

            “alTaskId”: “[parameters(‘al’).context.taskId]”,

            “alUser”: “[parameters(‘al’).context.user]”,

            “alComment”: “[parameters(‘al’).context.comment]”,

            “alItemType”: “[parameters(‘al’).context.item.type]”,

            “alItemId”: “[parameters(‘al’).context.item.id]”,

            “alItemName”: “[parameters(‘al’).context.item.name]”,

            “alItemVersion”: “[parameters(‘al’).context.item.version.name]”,

            “alConfigId”: “[parameters(‘al’).context.config.id]”,

            “alConfigName”: “[parameters(‘al’).context.config.name]”

        },

        “name”: “[concat(parameters(‘al’).context.item.name, ‘.’, replace(parameters(‘al’).context.config.id, ‘-‘, ”), ‘.’, parameters(‘al’).context.item.id)]”,

        “version”: “[parameters(‘al’).context.item.version.name]”,

        “galleryName”: “[variables(‘custom’).gallery]”,

        “generation”: “[if(contains(variables(‘custom’), ‘generation’), variables(‘custom’).generation, ‘V2’)]”

    },

    “resources”: [

        {

            “type”: “Microsoft.Compute/galleries/images”,

            “name”: “[concat(variables(‘galleryName’), ‘/’, variables(‘name’))]”,

            “apiVersion”: “2021-07-01”,

            “location”: “[variables(‘location’)]”,

            “tags”: “[variables(‘tags’)]”,

            “properties”: {

                “description”: “[parameters(‘al’).context.item.description]”,

                “hyperVGeneration”: “[variables(‘generation’)]”,

                “osType”: “Windows”,

                “osState”: “Specialized”,

                “endOfLifeDate”: “2030-01-01T00:00:00Z”,

                “identifier”: {

                    “publisher”: “Citrix”,

                    “offer”: “[parameters(‘al’).context.config.id]”,

                    “sku”: “[parameters(‘al’).context.item.id]”

                }

            },

            “resources”: [

                {

                    “type”: “versions”,

                    “apiVersion”: “2021-07-01”,

                    “name”: “[variables(‘version’)]”,

                    “location”: “[variables(‘location’)]”,

                    “dependsOn”: [

                        “[resourceId(‘Microsoft.Compute/galleries/images’, variables(‘galleryName’), variables(‘name’))]”

                    ],

                    “tags”: “[variables(‘tags’)]”,

                    “properties”: {

                        “publishingProfile”: {

                            “replicaCount”: 1,

                            “targetRegions”: [

                                {

                                    “name”: “[variables(‘location’)]”

                                }

                            ]

                        },

                        “storageProfile”: {

                            “osDiskImage”: {

                                “source”: {

                                    “id”: “[parameters(‘al’).input.source.diskId]”

                                }

                            }

                        }

                    }

                }

            ]

        }

    ],

    “outputs”: {

        “id”: {

            “type”: “string”,

            “value”: “[resourceId(‘Microsoft.Compute/galleries/images/versions’, variables(‘galleryName’), variables(‘name’), variables(‘version’))]”

        }

    }

}

Import JSON Templates into Azure Template Specs:

Once the JSON ARM Template Files have been successfully create, import these into Azure Template Specs within MS Azure.

Within Azure, navigate to Template Specs:

Select “Import Template” > Browse > Select the 4 Starter Templates created in the previous task > Select “Import“.

Once the files have been imported, review in Azure:

Now that the JSON files associated with each Starter Template have been imported, we can navigate back to the ALMC > Select Connectors > Azure Deployments Connector Configuration > Browse to Templates for each deployment type:

**As you can see, the Managed Identity created earlier has taken effect and we now have visibility and access to our template specs residing in the Resource Group**

For the Starter Templates, there is some custom data properties that are required for each template which we must specify in the Custom Data Section of the Connector Configuration:

As you can see in the Requirements for Boot Image and Layered Image, we need to have an Azure Compute Gallery. Therefore, we create a compute gallery in Azure:

Create an Azure Compute Gallery

Within this subtask, we will create an Azure Compute Gallery, which we will leverage later to store our Image Templates. See the steps below to create a Compute Gallery in Azure.

In Azure > Compute Galleries > Select Create:

When creating the Azure Compute Gallery, you will need to specify the following details:

  • Subscription
  • Resource Group
  • Name
  • Region
  • Description(optional)

Now, we have successfully created an Azure Compute Gallery, we can revisit the custom data properties within the App Layering Azure Deployments Connector Configuration.

Example Custom Data we decided to use for this example for all Deployment Types:

As you can see here, we specify the Gallery as the Azure Compute Gallery created in the previous task in the below Custom Data:

{

   “location”: “northeurope”,

   “gallery”: “strgalleryapplayering“,

   “storageSku”: “Premium_LRS”,

   “generation”: “V2”,

   “vmSize”: “Standard_D4s_v3”,

   “subnetId”: “/subscriptions/<SubscriptionID>/resourceGroups/Gavin_Strong_RG1/providers/Microsoft.Network/virtualNetworks/Gavin_Strong_RG1-vnet/subnets/default”

}

You are probably asking, where do we find this detail in Azure related to the SubnetId, and the location for example.- Lets take a look in Azure:

  • In Azure, navigate to your Virtual Network that you will use to deploy layers and publish layered images.
  • Once you are in the overview section of your Virtual Network, select on “JSON View” in the top right as seen in the screenshot below:

In the JSON View, we can obtain the SubnetId and Location details which we then specified as custom data parameters within the Azure Deployments Connector Configuration:

Within the Azure Deployments Connector Configuration – We can use this information to specify the gallery, location and Subnet Id in the Custom Data properties under the “Defaults” section, Using “Defaults” will apply to all deployment templates. Please note you can apply custom data specifically to each deployment template.

Please note, you can configure custom data specific to each Deployment Type also(The example above applied custom data parameters to all Deployment types):

Now we have successfully Configured the Template Specs and Configured the Connector for Azure Deployments, can now begin to create and configure our OS Layer.

Final Configuration details of AzureDeployments Connector:

Creating an OS Layer:

Firstly, we need to provision a Machine in Azure, this will be the OS Layer for the Deployment.

In this example, we provisioned a Windows Server 2022 Virtual Machine.

Disable Windows updates, etc on the OS Layer:

To disable Windows updates on the OS Layer, open Group Policy editor locally on the VM: Run > gpedit.msc

Local Group Policy > Administrative Templates > Windows Components > Windows Update > Configure Automatic Updates > Disabled.

After Applying the “Configure Automatic Updates” Policy, run “gpupdate /force” within Command Prompt (as Administrator).

Next, Install App Layering Machine Tools – You will be prompted to Use KMS or Do not use KMS. In this example I opted to use KMS.

Execute machine preparation utility in C:\Windows\Setup\Scripts – execute setup_x64.exe (as Administrator).

Run the App Layering Setup_x64 executable:

Leave Default – Click Next:

Once installation has completed, click Finish:

Execute “ngen.exe update /force” in the following directories:

  • c:\windows\microsoft.net\framework\v4.0.30319
  • c:\windows\microsoft.net\framework64\v4.0.30319

To stop Windows from performing maintenance and consuming 100% CPU, set the following registry value:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Maintenance

MaintenanceDisabled (REG_DWORD) = 1

Reboot the compositing machine.

After successful reboot, Open PowerShell (as Administrator)

Execute the Script:

C:\Windows\Setup\scripts\ImportOsLayer.ps1 -ElmAddress 10.0.0.201 -IgnoreCertErrors

 The script then prompts you for details about the new OS layer:

  • LayerName (required): Windows Server 2022
  • VersionName (required): 0.1
  • LayerSizeGib (required, but defaults to 60 GB): 60 GB
  • LayerDescription (optional): Windows Server 2022 (Not Domain Joined)
  • VersionDescription (optional): –
  • Comment (optional): –

Once you’ve entered the required information, the script reboots the system into the compositing engine, imports the OS, and builds the layer. Monitor the progress of the job in the App Layering management console

Within the App Layering Management Console > Tasks > We can now see that the script has rebooted into the Compositing engine, which imports the OS and builds the OS Layer. We can monitor this progress within the Tasks section of ALMC:

Once the Task has complete, we can verify in the ALMC:

We can also see the Corresponding OS Layer created within the App Layering Management Console > Layers > OS Layer:

Platform Layer Creation:

A platform layer includes the platform software and settings required for your layers and layered images to run flawlessly in your environment.

You can create platform layers for two purposes:

For creating and packaging layers: When you’ve imported the OS from a different hypervisor than the one where you create your layers, use this type of platform layer to create app layers.

For publishing layered images: Use this type of Platform layer in your image template so that the published layered images run flawlessly in your environment.

To Create Platform Layer > Go to the App Layering Management Console > Layers > Platform Layers > Create New Layer:

  • Layer Name: Windows Server 2022 Platform Layer
  • Layer Description(Optional):
  • Choose an Icon(Optional):
  • Initial Version Name: v1.0
  • Initial Version Description(Optional)
  • Max Layer Size (GB): 10
  • OS Layer: Windows Server 2022 [Select the Desired OS Layer to create the Layer with]
  • Platform Type: This Platform layer will be used for publishing images
  • Hypervisor:  Microsoft Azure
  • Provisioning Service: Machine Creation (MCS)
  • Connection Broker: Citrix Virtual Desktops ( DaaS)
  • Connector Configuration: Azure Deployments – CitrixAppLayeringTest [This will be different for every environment]
  • Packaging Disk Filename: Windows Server 2022 Platform Layer (inherits the “Layer Name” by default, but can be changed)

Once you have completed configuration, select “Confirm and Complete”.

You will be presented with the Layer Summary, review details specified and Create Layer to proceed.

At first, under Platform Layers, you will notice that the Platform Layer created will be labelled as “Not Deployable”. Navigate to “Tasks” to check the status of the job.

Within the Tasks view, we can see that the Task to create the Platform Layer is still Running:

Select on the Running Task, and select “View Details”, it will provide a progress status of the Task.

Once the task has been complete, it will notify “Action Required” to indicate that the packaging machine has been created and we can now logon to install software for the platform layer before proceeding with the finalization process.

Take note of the hostname of the packaging machine, as we will need to locate this machine in the Azure Resource Group.

Navigate to Azure and Search for the Machine named al-1376266-vm in this example above. (hostname will differ for every environment)

Take note of the IP Address of the Machine, and connect to the Packaging machine so we can install the required Software.

In this example task, we will simply joined the Machine to the Domain, and we will install the Citrix VDA Software. In a typical production environment, the Platform Layer would usually contains Anti Virus, Hypervisor Tools, VDA, WEM Agent, etc.

Run NGEN before Finalization of the Platform Layer

After we have installed all of the relevant Software for the desired Platform Layer, next, we need to prepare the image for finalization by using NGEN. The reason we need to use NGEN is to update the image accordingly when there are .NET Changes made to the image as a result of software installations for example. Without running NGEN, App Layering will be unable to begin the Finalization process of the Platform Layer.

Navigate to C:\Windows\Microsoft.NET\Framework\v4.0.30319 in command line and run ngen update /force

Next, navigate to C:\Windows\Microsoft.NET\Framework64\v4.0.30319 in command line and run ngen update /force

Once we have run ngen successfully in both the Framework and Framework64 locations, we can reboot the image before beginning Image Finalization.

Please Note: Sometimes it might require 2 to 3 reboots.

Note: In Some Scenarios, if the Finalization process is failing after running NGEN, we may need to run ngen eqi 3

Once you Select Shutdown for Finalization, this will kick off the Finalization Process and the Machine will be restarted to go through the process. If there is any issues or if ngen is still running, the Finalization process will return an error message.

If you are having issues with Finalization, you can refer to the following Citrix Support Article Debugging Layer Integrity Problems in Citrix App Layering  

Navigate back to the App Layering Management Console via the Browser > Go to Tasks and check the Status of the Platform Layer Task which previously had the alert “Action Required”. It should show that the Platform Layer Creation Process is now “Running” again.

In the below example, the Finalization process has been completed and the Compositing engine VM al-1376266-vm  will be de-provisioned from Azure.

Once the task is successful, navigate to “Layers” and Select “Platform Layers” – The Windows server 2022 Platform Layer has been created successfully and is displayed as “Deployable”.

App Layer Creation:

  • App layers: The layers where you install applications. Typically, we recommend installing one app on each layer, though you can include more. For easy maintenance, include apps that are on the same update schedule. If an application requires other apps, create the layer for the required application first. For more about creating an app layer, see Create or clone an app layer. For tips about layering a specific application, see App Layering Recipes.

In the App Layering Management Console, go to Layers > App Layers > Create App Layer:

Specify Details for App Layer:

  • Layer Name:
  • Layer Description (optional):
  • Choose an Icon (optional):
  • Initial Version Name: v1.0
  • Initial Version Description (optional):
  • Max Layer Size (GB): 10
  • OS Layer: Selected Windows Server 2022 in this example
  • Connector Configuration:
  • Packaging Disk Filename:

Review Details and Create Layer:

Similar to Platform Layer creation, you will initially see that the App Layer is not yet Deployable. Navigate to Tasks within the ALMC.

Within Tasks, we can see that the App Layer is being created and the Compositing engine VM is being created on Azure so we can install the Lightshot Application and Finalize the App Layer.

After a couple of minutes, when the packaging machine has been created on the compositing engine VM, The Task will alert “Action Required”. RDP to the Machine al-1376267-vm and begin installing the applications.

Once the relevant Applications are installed, Select “Shutdown for Finalize”:

Shutdown for Finalize:

Navigate back to the App Layering Management Console and you can see that the Task is now running again and the Compositing engine VM is being deleted/de-provisioned on the Azure side.

Compositing engine VM no longer exists in Azure:

Task has completed Successfully, we have now created our app layer:

Navigate back to App Layers in the App Layering Management Console and confirm the App Layer is now “Deployable”:

Building an Image Template

In the next task, we will take our OS Layer, Platform Layer, and App Layer to create a Layered Image which we can later use in Citrix DaaS to create a Machine Catalog using Machine Creation Services.

Navigate to Images in the App Layering Management Console > Create Template.

To create an image template:

  1. In the App Layering management console, select the Images module, then click Create Template.
  2. Enter a Name for the template and notes in the Description field (optional), so you can identify the template when choosing one for publishing a layered image.
  3. Select one of the Available OS Layers. If there is more than one layer version, the most recent version is selected by default. You can choose an older version by expanding the layer and choosing a different one.
  4. Select the App Layers > Edit Selection to include app layers in the layered images that you publish using this template.
  5. Select a Platform layer with the tools and hardware settings that you must publish layered images to your environment.
  6. Choose a Connector Configuration for the platform where you are creating this layer. If the configuration you need isn’t listed, you need to create the connector configuration from the Connectors page.
  7. Edit the following fields, as needed:
    • (Optional) Layered Image Disk File name. Enter a name for the layered image disk.
    • Layered Image Partition Size. The default disk size of 100 GB is recommended.
    • Layered Image Disk Format. The default disk format is VHD, but you can also select VMDK or QCOW2. If you are publishing to the appliance’s File Share instead of using a connector configuration, this setting allows you to choose a disk format compatible with the environment to which you are copying the disk.
    • Sysprep. The options available depend upon the hypervisor or Provisioning Service specified in your connector configuration:
      • Azure, Hyper-V, Citrix Hypervisor, Nutanix, vSphere: Defaults to Generalized Offline. (For Azure, this is the only option.)Machine creation, Citrix Provisioning, View: Not generalized is the only option for Machine creation, Citrix Provisioning, and View running on any of the hypervisors
      • File Share: Defaults to Not generalized, if using a File Share instead of a connector configuration.
    • Elastic Layering – Select the Application Layering option to activate Elastic Layering on this layered image.
    • User Layer- When enabled in System Settings, you can select Full User layers (Labs), Office 365 (desktop), or Session Office 365 (server OS) option. Choose the Full option to save the settings and data for users independent of specific applications. Choose Office 365 or Session Office 365 to save the settings and data for Outlook 365 running on a desktop system or a session host.
  8. Select Confirm and Complete, and enter any comments you would like for this layer.
  9. Click Create Template to save your changes, or Create Template and Publish to save the template and then publish the layered images. The new template icon appears in the Images module.

Review Configuration andSelect Create Template.

The Template is now Publishable within the App Layering Management Console:

Select on the Image Template and “Publish Layered Image”

Select “Publish Layered Image”

Navigate back to Tasks > Check the Status of the Running Task:

The real-time status of the Task is available to see:

Once the Job is complete you will be provided with a URL to the Azure Compute Gallery which we earlier specified in theAzure Deployments Connector Configuration Custom Data – this is where the Layered Image Template resides:

In Azure, Go to Azure Compute Galleries:

Select the strgalleryapplayering, which we created in Azure and specified in the custom data section of the Azure Deployments Connector config earlier:

Within the strgalleryapplayering compute gallery, we can now see the Image Template str-Win2022-test-AL

In JSON View, we have the computer Gallery ID and the Location details specified earlier.

Creating a Machine Catalog in Citrix DaaS using the Layered Image

Creation of the MCS Catalog is no different, except when selecting the Master Image, you select the layered image template that exists in the Azure Compute Gallery where the template resides.

Tips & Tricks

App Layering Users and Groups

Ref: App Layering Users and Groups

The OS layer preserves any local users or groups that you add, but app layers, platform layers, user layers, and elastic layers DO NOT. For example, users and groups that you add or change while installing an application on an app layer, platform layer, or user layer don’t persist. Either add the local user or administrator to the OS layer before installing the application, or consider installing the application on the OS layer.

Advertisement

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.