Quickly deploy RDS 2016 in Azure


In this post I'll show how to deploy a minimal Windows Server 2016 Remote Desktop Services farm in Azure in 20 minutes using Azure Resource Manager template. Such farm will be enough to provide Desktop-as-a-Service (DaaS) for 5 to 100 end-users. It was designed as a single-tenant solution, then can be quickly deployed in Azure with minimal effort for every customer. My ARM template includes:

  1. One VM with Active Directory Domain Controller and DNS roles
  2. One VM with RD Connection Broker and RD Licensing Server (per-user licensing through SPLA)
  3. One VM with RD Gateway and RD Web
  4. Specified number of RD Session Hosts.

My ARM template is based on RDS 2016 Base template from GitHub. I recommend you to watch this video from Ignite to understand the details. The resulting farm isn't protected from DC, CB or GW failures, but it is much cheaper then Full-HA deployment with a pair of Connection Brokers with Azure SQL Database, a pair of Gateways with a Load Balancer and a highly available AD environment. If you need an RDS farm for 100+ end-users, I recommend you to modify my template and add high availability for the core components, add Azure Backup capabilities and Azure SMT for centralized management. But I assume that such large RDS farms are rare among CSP partners, and those capabilities can be added manually instead of creating a super-complex ARM template.

Login to the Azure Portal and click "More Services" in the left pane. Choose "Template" menu in the list and then click "Add" to create a new Azure Resource Manager Template.

capture_09012017_180344

Specify the template name and description:

capture_09012017_181419Then copy the following text and paste it to the template:

capture_09012017_181657

{
   "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
   "contentVersion": "1.0.0.0",
   "parameters": {
     "gwdnsLabelPrefix": {
       "type": "string",
       "metadata": {
         "description": "Unique gateway public DNS prefix. The fqdn will look something like 'prefix.region.cloudapp.azure.com'. Up to 62 chars, digits or dashes, lowercase, should start with a letter."
       }
     },
     "adDomainName": {
       "type": "string",
       "metadata": {
         "description": "The name of the AD domain."
       },
       "defaultValue": "contoso.com"
     },
     "adminUsername": {
       "type": "string",
       "metadata": {
         "description": "The name of the domain admin. Can't be 'administrator' or 'user'."
       },
       "defaultValue": "rdsadmin"
     },
     "adminPassword": {
       "type": "securestring",
       "metadata": {
         "description": "The password for the administrator account of the new VM and the domain"
       }
     },
     "numberOfRdshInstances": {
       "type": "int",
       "defaultValue": 1,
       "metadata": {
         "description": "Initial number of RD Session Hosts"
       }
     },
     "rdshVmSize": {
       "type": "string",
       "allowedValues": [
         "Standard_A2m_V2",
         "Standard_A4m_V2",
         "Standard_A8m_V2",
         "Standard_D11_V2",
         "Standard_D12_V2",
         "Standard_D13_V2",
         "Standard_D14_V2",
         "Standard_D15_V2"
       ],
       "metadata": {
         "description": "The size of the RD Session Host VMs"
       },
       "defaultValue": "Standard_D11_V2"
     }
   },
   "variables": {
     "adAssetLocation": "https://raw.githubusercontent.com/Azure/AzureStack-QuickStart-Templates/master/ad-non-ha",
     "gwpublicIPAddressName": "gwpip",
     "imageSKU": "2016-Datacenter",
     "adVMSize": "Basic_A1",
     "adVnetName": "[concat('ADVNET',resourceGroup().name)]",
     "adSubnetName": "[concat('ADStaticSubnet',resourceGroup().name)]",
     "vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('adVnetName'))]",
     "staticSubnetID": "[concat(variables('vnetID'),'/subnets/', variables('adSubnetName'))]",
     "adTemplateURL": "[concat(variables('adAssetLocation'),'/adVmTemplate.json')]",
     "adStorageName": "[tolower(concat('adsa',uniqueString(resourceGroup().id)))]",
     "adVmDeployment": "CreateAdVms",
     "adVmDeploymentId": "[concat('Microsoft.Resources/deployments/', variables('adVmDeployment'))]",
     "deployPrimaryAdTemplateURL": "[concat(variables('adAssetLocation'),'/deployPrimaryAD.json')]",
     "deployPrimaryAd": "DeployPrimaryAd",
     "deployPrimaryAdID": "[concat('Microsoft.Resources/deployments/', variables('deployPrimaryAd'))]",
     "adPDCVMName": "DC1",
     "vnetwithDNSTemplateURL": "[concat(variables('adAssetLocation'),'/vnet-with-dns-server.json')]",
     "updateVNetDNS1": "updateVNetDNS",
     "updateVNetDNS1ID": "[concat('Microsoft.Resources/deployments/', variables('updateVNetDNS1'))]",
     "nicTemplateURL": "[concat(variables('adAssetLocation'),'/nic.json')]",
     "publicLBName": "[concat('ADPLB',resourceGroup().name)]",
     "publicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]",
     "lbFE": "ADLBFE",
     "rdpNAT": "ADRDPNAT",
     "publiclbID": "[resourceId('Microsoft.Network/loadBalancers',variables('publicLBName'))]",
     "publiclbFEConfigID": "[concat(variables('publiclbID'),'/frontendIPConfigurations/',variables('lbFE'))]",
     "rdpPort": 3389,
     "adRDPNATRuleID": "[concat(variables('publiclbID'),'/inboundNatRules/',variables('rdpNAT'))]",
     "adNICName": "[concat('ADNic',resourceGroup().name)]",
     "lbBE": "ADLBBE",
     "publicBEAddressPoolID": "[concat(variables('publiclbID'),'/backendAddressPools/',variables('lbBE'))]",
     "gwLBName": "[concat('GWPLB',resourceGroup().name)]",
     "publicIPAddressName": "[tolower(concat('adpip',uniqueString(resourceGroup().Id)))]",
     "gwIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',variables('gwpublicIPAddressName'))]",
     "gwlbFE": "GWLBFE",
     "gwlbID": "[resourceId('Microsoft.Network/loadBalancers',variables('gwLBName'))]",
     "gwlbFEConfigID": "[concat(variables('gwlbID'),'/frontendIPConfigurations/',variables('gwlbFE'))]",
     "gwlbBE": "GWLBBE",
     "gwBEAddressPoolID": "[concat(variables('gwlbID'),'/backendAddressPools/',variables('gwlbBE'))]",
     "dnsLabelPrefix": "[tolower(concat('adns', resourceGroup().name))]",
     "storageAccountName": "[tolower(concat('rdsa',uniqueString(resourceGroup().id)))]",
     "storageAccountType": "Standard_LRS",
     "uniqueStorageAccountContainerName": "[tolower(concat('sc', uniqueString(resourceGroup().id)))]",
     "imagePublisher": "MicrosoftWindowsServer",
     "imageOffer": "WindowsServer",
     "vnetAddressRange": "10.0.0.0/16",
     "subnetAddressRange": "10.0.0.0/24",
     "dnsServerPrivateIp": "10.0.0.4",
     "subnet-id": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('adVnetName')),'/subnets/', variables('adSubnetName'))]",
     "assetLocation": "https://raw.githubusercontent.com/Azure/azure-QuickStart-Templates/master/rds-deployment/",
     "nsgName": "RDSNsg",
     "nsgID": "[resourceId('Microsoft.Network/networkSecurityGroups',variables('nsgName'))]",
     "subnets": [
       {
         "name": "[variables('adSubnetName')]",
         "properties": {
           "addressPrefix": "[variables('subnetAddressRange')]",
           "networkSecurityGroup": {
             "id": "[variables('nsgID')]"
           }
         }
       }
     ]

  },
   "resources": [
     {
       "name": "[variables('nsgName')]",
       "type": "Microsoft.Network/networkSecurityGroups",
       "location": "[resourceGroup().location]",
       "apiVersion": "2015-06-15",
       "properties": {
         "securityRules": [
           {
             "name": "rule1",
             "properties": {
               "protocol": "*",
               "sourcePortRange": "*",
               "destinationPortRange": "*",
               "sourceAddressPrefix": "*",
               "destinationAddressPrefix": "*",
               "access": "Allow",
               "priority": 101,
               "direction": "Inbound"
             }
           }
         ]
       }
     },
     {
       "name": "[variables('adVnetName')]",
       "type": "Microsoft.Network/virtualNetworks",
       "location": "[resourceGroup().location]",
       "apiVersion": "2015-06-15",
       "dependsOn": [
         "[variables('nsgID')]"
       ],
       "properties": {
         "addressSpace": {
           "addressPrefixes": [
             "[variables('vnetAddressRange')]"
           ]
         },
         "subnets": [
           {
             "name": "[variables('adSubnetName')]",
             "properties": {
               "addressPrefix": "[variables('subnetAddressRange')]",
               "networkSecurityGroup": {
                 "id": "[variables('nsgID')]"
               }
             }
           }
         ]
       }
     },
     {
       "name": "[variables('publicIPAddressName')]",
       "type": "Microsoft.Network/publicIPAddresses",
       "location": "[resourceGroup().location]",
       "apiVersion": "2015-06-15",
       "dependsOn": [
         "[variables('vnetID')]"
       ],
       "properties": {
         "publicIPAllocationMethod": "Dynamic",
         "dnsSettings": {
           "domainNameLabel": "[variables('dnsLabelPrefix')]"
         }
       }
     },
     {
       "name": "[variables('gwpublicIPAddressName')]",
       "type": "Microsoft.Network/publicIPAddresses",
       "location": "[resourceGroup().location]",
       "apiVersion": "2015-06-15",
       "dependsOn": [
         "[variables('deployPrimaryAdID')]"
       ],
       "properties": {
         "publicIPAllocationMethod": "Static",
         "dnsSettings": {
           "domainNameLabel": "[parameters('gwdnsLabelPrefix')]"
         }
       }
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Compute/availabilitySets",
       "name": "gw-availabilityset",
       "location": "[resourceGroup().location]"
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Compute/availabilitySets",
       "name": "cb-availabilityset",
       "location": "[resourceGroup().location]"
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Compute/availabilitySets",
       "name": "rdsh-availabilityset",
       "location": "[resourceGroup().location]"
     },
     {
       "name": "[variables('publiclbName')]",
       "type": "Microsoft.Network/loadBalancers",
       "apiVersion": "2015-06-15",
       "location": "[resourceGroup().location]",
       "dependsOn": [
         "[variables('publicIPAddressID')]"
       ],
       "properties": {
         "frontendIPConfigurations": [
           {
             "name": "[variables('lbFE')]",
             "properties": {
               "publicIPAddress": {
                 "id": "[variables('publicIPAddressID')]"
               }
             }
           }
         ],
         "backendAddressPools": [
           {
             "name": "[variables('lbBE')]"
           }
         ],
         "inboundNatRules": [
           {
             "name": "[variables('rdpNAT')]",
             "properties": {
               "frontendIPConfiguration": {
                 "id": "[variables('publiclbFEConfigID')]"
               },
               "protocol": "tcp",
               "frontendPort": "[variables('rdpPort')]",
               "backendPort": 3389,
               "enableFloatingIP": false
             }
           }
         ]
       }
     },
     {
       "name": "[variables('gwlbName')]",
       "type": "Microsoft.Network/loadBalancers",
       "apiVersion": "2015-06-15",
       "location": "[resourceGroup().location]",
       "dependsOn": [
         "[variables('gwIPAddressID')]"
       ],
       "properties": {
         "frontendIPConfigurations": [
           {
             "name": "[variables('gwlbFE')]",
             "properties": {
               "publicIPAddress": {
                 "id": "[variables('gwIPAddressID')]"
               }
             }
           }
         ],
         "backendAddressPools": [
           {
             "name": "[variables('gwlbBE')]"
           }
         ],
         "loadBalancingRules": [
           {
             "name": "LBRule01",
             "properties": {
               "frontendIPConfiguration": {
                 "id": "[variables('gwlbFEConfigID')]"
               },
               "backendAddressPool": {
                 "id": "[variables('gwBEAddressPoolID')]"
               },
               "protocol": "Tcp",
               "frontendPort": 443,
               "backendPort": 443,
               "enableFloatingIP": false,
               "idleTimeoutInMinutes": 5,
               "loadDistribution": "SourceIPProtocol",
               "probe": {
                 "id": "[concat(variables('gwlbID'),'/probes/tcpProbe')]"
               }
             }
           },
           {
             "name": "LBRule02",
             "properties": {
               "frontendIPConfiguration": {
                 "id": "[variables('gwlbFEConfigID')]"
               },
               "backendAddressPool": {
                 "id": "[variables('gwBEAddressPoolID')]"
               },
               "protocol": "Udp",
               "frontendPort": 3391,
               "backendPort": 3391,
               "enableFloatingIP": false,
               "idleTimeoutInMinutes": 5,
               "loadDistribution": "SourceIPProtocol",
               "probe": {
                 "id": "[concat(variables('gwlbID'),'/probes/tcpProbe')]"
               }
             }
           }
         ],
         "probes": [
           {
             "name": "tcpProbe",
             "properties": {
               "protocol": "Tcp",
               "port": 443,
               "intervalInSeconds": 5,
               "numberOfProbes": 2
             }
           },
           {
             "name": "tcpProbe01",
             "properties": {
               "protocol": "Tcp",
               "port": 3391,
               "intervalInSeconds": 5,
               "numberOfProbes": 2
             }
           }
         ],
         "inboundNatRules": [
           {
             "name": "rdp",
             "properties": {
               "frontendIPConfiguration": {
                 "id": "[variables('gwlbFEConfigID')]"
               },
               "protocol": "tcp",
               "frontendPort": 3389,
               "backendPort": 3389,
               "enableFloatingIP": false
             }
           }

        ]
       }
     },
     {
       "name": "[variables('adVmDeployment')]",
       "type": "Microsoft.Resources/deployments",
       "apiVersion": "2016-02-01",
       "dependsOn": [
         "[variables('publiclbID')]"
       ],
       "properties": {
         "mode": "Incremental",
         "templateLink": {
           "uri": "[variables('adTemplateURL')]",
           "contentVersion": "1.0.0.0"
         },
         "parameters": {
           "adminUsername": {
             "value": "[parameters('adminUsername')]"
           },
           "adminPassword": {
             "value": "[parameters('adminPassword')]"
           },
           "adRDPNATRuleID": {
             "value": "[variables('adRDPNATRuleID')]"
           },
           "storageAccount": {
             "value": "[variables('adStorageName')]"
           },
           "subnetResourceId": {
             "value": "[variables('staticSubnetID')]"
           },
           "primaryAdIpAddress": {
             "value": "[variables('dnsServerPrivateIp')]"
           },
           "storageAccountType": {
             "value": "[variables('storageAccountType')]"
           },
           "vmName": {
             "value": "[variables('adPDCVMName')]"
           },
           "vmSize": {
             "value": "[variables('adVMSize')]"
           },
           "adDNicName": {
             "value": "[variables('adNICName')]"
           }
         }
       }
     },
     {
       "name": "[variables('deployPrimaryAd')]",
       "type": "Microsoft.Resources/deployments",
       "apiVersion": "2016-02-01",
       "dependsOn": [
         "[variables('adVmDeploymentID')]"
       ],
       "properties": {
         "mode": "Incremental",
         "templateLink": {
           "uri": "[variables('deployPrimaryAdTemplateURL')]",
           "contentVersion": "1.0.0.0"
         },
         "parameters": {
           "primaryADName": {
             "value": "[variables('adPDCVMName')]"
           },
           "domainName": {
             "value": "[parameters('adDomainName')]"
           },
           "adminUsername": {
             "value": "[parameters('adminUsername')]"
           },
           "adminPassword": {
             "value": "[parameters('adminPassword')]"
           },
           "assetLocation": {
             "value": "[variables('adAssetLocation')]"
           }
         }
       }
     },
     {
       "name": "[variables('updateVNetDNS1')]",
       "type": "Microsoft.Resources/deployments",
       "apiVersion": "2016-02-01",
       "dependsOn": [
         "[variables('deployPrimaryAdID')]"
       ],
       "properties": {
         "mode": "Incremental",
         "templateLink": {
           "uri": "[variables('vnetwithDNSTemplateURL')]",
           "contentVersion": "1.0.0.0"
         },
         "parameters": {
           "virtualNetworkName": {
             "value": "[variables('adVnetName')]"
           },
           "virtualNetworkAddressRange": {
             "value": "[variables('vnetAddressRange')]"
           },
           "subnets": {
             "value": "[variables('subnets')]"
           },
           "dnsServerAddress": {
             "value": [
               "[variables('dnsServerPrivateIp')]"
             ]
           }
         }
       }
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Storage/storageAccounts",
       "name": "[variables('storageAccountName')]",
       "location": "[resourceGroup().location]",
       "properties": {
         "accountType": "[variables('storageAccountType')]"
       }
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Network/networkInterfaces",
       "name": "gw-nic",
       "location": "[resourceGroup().location]",
       "dependsOn": [
         "[variables('gwlbID')]",
         "[variables('adVmDeploymentID')]"
       ],
       "properties": {
         "ipConfigurations": [
           {
             "name": "ipconfig",
             "properties": {
               "privateIPAllocationMethod": "Dynamic",
               "subnet": {
                 "id": "[variables('subnet-id')]"
               },
               "loadBalancerBackendAddressPools": [
                 {
                   "id": "[variables('gwBEAddressPoolID')]"
                 }
               ],
               "loadBalancerInboundNatRules": [
                 {
                   "id": "[concat(variables('gwlbID'),'/inboundNatRules/rdp')]"
                 }
               ]
             }
           }
         ],
         "dnsSettings": {
           "dnsServers": [
             "[variables('dnsServerPrivateIp')]"
           ]
         }
       }
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Network/networkInterfaces",
       "name": "cb-nic",
       "location": "[resourceGroup().location]",
       "dependsOn": [
         "[variables('publiclbID')]",
         "[variables('adVmDeploymentID')]"
       ],
       "properties": {
         "ipConfigurations": [
           {
             "name": "ipconfig",
             "properties": {
               "privateIPAllocationMethod": "Dynamic",
               "subnet": {
                 "id": "[variables('subnet-id')]"
               }
             }
           }
         ],
         "dnsSettings": {
           "dnsServers": [
             "[variables('dnsServerPrivateIp')]"
           ]
         }
       }
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Network/networkInterfaces",
       "name": "[concat('rdsh-', copyindex(), '-nic')]",
       "location": "[resourceGroup().location]",
       "copy": {
         "name": "rdsh-nic-loop",
         "count": "[parameters('numberOfRdshInstances')]"
       },
       "dependsOn": [
         "[variables('publiclbID')]",
         "[variables('adVmDeploymentID')]"
       ],
       "properties": {
         "ipConfigurations": [
           {
             "name": "ipconfig",
             "properties": {
               "privateIPAllocationMethod": "Dynamic",
               "subnet": {
                 "id": "[variables('subnet-id')]"
               }
             }
           }
         ],
         "dnsSettings": {
           "dnsServers": [
             "[variables('dnsServerPrivateIp')]"
           ]
         }
       }
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Compute/virtualMachines",
       "name": "GW1",
       "location": "[resourceGroup().location]",
       "dependsOn": [
         "[variables('deployPrimaryAdID')]",
         "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
         "Microsoft.Network/networkInterfaces/gw-nic"
       ],
       "properties": {
         "hardwareProfile": {
           "vmSize": "Standard_A2"
         },
         "availabilitySet": {
           "id": "[resourceId('Microsoft.Compute/availabilitySets', 'gw-availabilityset')]"
         },
         "osProfile": {
           "computerName": "GW1",
           "adminUsername": "[parameters('adminUsername')]",
           "adminPassword": "[parameters('adminPassword')]"
         },
         "storageProfile": {
           "imageReference": {
             "publisher": "[variables('imagePublisher')]",
             "offer": "[variables('imageOffer')]",
             "sku": "[variables('imageSKU')]",
             "version": "latest"
           },
           "osDisk": {
             "name": "osdisk",
             "vhd": {
               "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob,variables('uniqueStorageAccountContainerName'),'/','gw-vm-os-disk.vhd')]"
             },
             "caching": "ReadWrite",
             "createOption": "FromImage"
           }
         },
         "networkProfile": {
           "networkInterfaces": [
             {
               "id": "[resourceId('Microsoft.Network/networkInterfaces','gw-nic')]"
             }
           ]
         }
       },
       "resources": [
         {
           "apiVersion": "2015-06-15",
           "type": "Microsoft.Compute/virtualMachines/extensions",
           "name": "GW1/gateway",
           "location": "[resourceGroup().location]",
           "dependsOn": [
             "[resourceId('Microsoft.Compute/virtualMachines', 'GW1')]"
           ],
           "properties": {
             "publisher": "Microsoft.Powershell",
             "type": "DSC",
             "typeHandlerVersion": "2.11",
             "autoUpgradeMinorVersion": true,
             "settings": {
               "modulesUrl": "[concat(variables('assetLocation'),'/Configuration.zip')]",
               "configurationFunction": "Configuration.ps1\\Gateway",
               "Properties": {
                 "DomainName": "[parameters('adDomainName')]",
                 "AdminCreds": {
                   "UserName": "[parameters('adminUsername')]",
                   "Password": "PrivateSettingsRef:AdminPassword"
                 }
               }
             },
             "protectedSettings": {
               "Items": {
                 "AdminPassword": "[parameters('adminPassword')]"
               }
             }
           }
         }
       ]
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Compute/virtualMachines",
       "name": "[concat('RDSH', copyindex())]",
       "location": "[resourceGroup().location]",
       "copy": {
         "name": "rdsh-vm-loop",
         "count": "[parameters('numberOfRdshInstances')]"
       },
       "dependsOn": [
         "[variables('deployPrimaryAdID')]",
         "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
         "[concat('Microsoft.Network/networkInterfaces/', 'rdsh-', copyindex(), '-nic')]"
       ],
       "properties": {
         "hardwareProfile": {
           "vmSize": "[parameters('rdshVmSize')]"
         },
         "availabilitySet": {
           "id": "[resourceId('Microsoft.Compute/availabilitySets', 'rdsh-availabilityset')]"
         },
         "osProfile": {
           "computerName": "[concat('RDSH', copyIndex())]",
           "adminUsername": "[parameters('adminUsername')]",
           "adminPassword": "[parameters('adminPassword')]"
         },
         "storageProfile": {
           "imageReference": {
             "publisher": "[variables('imagePublisher')]",
             "offer": "[variables('imageOffer')]",
             "sku": "[variables('imageSKU')]",
             "version": "latest"
           },
           "osDisk": {
             "name": "osdisk",
             "vhd": {
               "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob,variables('uniqueStorageAccountContainerName'),'/','rdsh-',copyindex(),'-os-disk.vhd')]"

            },
             "caching": "ReadWrite",
             "createOption": "FromImage"
           }
         },
         "networkProfile": {
           "networkInterfaces": [
             {
               "id": "[resourceId('Microsoft.Network/networkInterfaces',concat('rdsh-', copyindex(), '-nic'))]"
             }
           ]
         }
       },
       "resources": [
         {
           "apiVersion": "2015-06-15",
           "type": "Microsoft.Compute/virtualMachines/extensions",
           "name": "[concat('RDSH', copyindex(),'/sessionhost')]",
           "location": "[resourceGroup().location]",
           "dependsOn": [
             "[resourceId('Microsoft.Compute/virtualMachines', concat('RDSH', copyindex()))]"
           ],
           "properties": {
             "publisher": "Microsoft.Powershell",
             "type": "DSC",
             "typeHandlerVersion": "2.11",
             "autoUpgradeMinorVersion": true,
             "settings": {
               "ModulesUrl": "[concat(variables('assetLocation'),'/Configuration.zip')]",
               "ConfigurationFunction": "Configuration.ps1\\SessionHost",
               "Properties": {
                 "DomainName": "[parameters('adDomainName')]",
                 "AdminCreds": {
                   "UserName": "[parameters('adminUsername')]",
                   "Password": "PrivateSettingsRef:AdminPassword"
                 }
               }
             },
             "protectedSettings": {
               "Items": {
                 "AdminPassword": "[parameters('adminPassword')]"
               }
             }
           }
         }
       ]
     },
     {
       "apiVersion": "2015-06-15",
       "type": "Microsoft.Compute/virtualMachines",
       "name": "CB1",
       "location": "[resourceGroup().location]",
       "dependsOn": [
         "[variables('deployPrimaryAdID')]",
         "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
         "Microsoft.Network/networkInterfaces/cb-nic",
         "rdsh-vm-loop"
       ],
       "properties": {
         "hardwareProfile": {
           "vmSize": "Standard_A2"
         },
         "availabilitySet": {
           "id": "[resourceId('Microsoft.Compute/availabilitySets', 'cb-availabilityset')]"
         },
         "osProfile": {
           "computerName": "CB1",
           "adminUsername": "[parameters('adminUsername')]",
           "adminPassword": "[parameters('adminPassword')]"
         },
         "storageProfile": {
           "imageReference": {
             "publisher": "[variables('imagePublisher')]",
             "offer": "[variables('imageOffer')]",
             "sku": "[variables('imageSKU')]",
             "version": "latest"
           },
           "osDisk": {
             "name": "osdisk",
             "vhd": {
               "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob,variables('uniqueStorageAccountContainerName'),'/','cb-vm-os-disk.vhd')]"
             },
             "caching": "ReadWrite",
             "createOption": "FromImage"
           }
         },
         "networkProfile": {
           "networkInterfaces": [
             {
               "id": "[resourceId('Microsoft.Network/networkInterfaces','cb-nic')]"
             }
           ]
         }
       }
     },
     {
       "type": "Microsoft.Compute/virtualMachines/extensions",
       "name": "CB1/rdsdeployment",
       "apiVersion": "2015-06-15",
       "location": "[resourceGroup().location]",
       "dependsOn": [
         "[resourceId('Microsoft.Compute/virtualMachines', 'CB1')]",
         "Microsoft.Compute/virtualMachines/GW1/extensions/gateway",
         "rdsh-vm-loop"
       ],
       "properties": {
         "autoUpgradeMinorVersion": true,
         "protectedSettings": {
           "Items": {
             "adminPassword": "[parameters('adminPassword')]"
           }
         },
         "publisher": "Microsoft.Powershell",
         "settings": {
           "modulesUrl": "[concat(variables('assetLocation'),'/Configuration.zip')]",
           "configurationFunction": "Configuration.ps1\\RDSDeployment",
           "Properties": {
             "adminCreds": {
               "UserName": "[parameters('adminUsername')]",
               "Password": "PrivateSettingsRef:adminPassword"
             },
             "connectionBroker": "[concat('CB1.',parameters('adDomainName'))]",
             "domainName": "[parameters('adDomainName')]",
             "externalfqdn": "[reference(variables('gwpublicIPAddressName')).dnsSettings.fqdn]",
             "numberOfRdshInstances": "[parameters('numberOfRdshInstances')]",
             "sessionHostNamingPrefix": "RDSH",
             "webAccessServer": "[concat('GW1.',parameters('adDomainName'))]"
           }
         },
         "type": "DSC",
         "typeHandlerVersion": "2.11"
       }
     }
   ]
 }

Then save the template, open it and click "Deploy":

capture_09012017_233030

Create a new Resource Group, specify the Region and provide some parameters:

  1. Unique RD Gateway public DNS prefix
  2. Active Directory Domain Name
  3. Domain Admin username and password
  4. Number of RD Session Hosts and their size. Agree with Terms and Conditions and click "Purchase".

capture_09012017_233152

In 20-30 minutes you'll have a ready-to-use RDS 2016 farm, accessible via specified Gwdns Label Prefix + Region URL. In my case it is https://kotlyarenkodaas.northeurope.cloudapp.azure.com/RDWeb.

capture_09012017_235615Manual steps after the deployment:

  1. Configure SSL certificates
  2. Create Session Host collections
  3. Install required applications for end-users
  4. Configure User Profile Disks
  5. Optional: Implement auto-scaling of RD Session Hosts
  6. Optional: Configure backup using Azure Backup.

That's all for today. Have fun with RDS 2016 and ARM!

Comments (13)

  1. steve says:

    Could the two VMs be consolidated into one, so runs both these workloads.
    1. One VM with Active Directory Domain Controller and DNS roles
    2. One VM with RD Connection Broker and RD Licensing Server (per-user licensing through SPLA)

    1. While Connection Broker installation on DC is supported since WS 2012 SP1, it is not recommended:
      1) Security reasons - hack of CB or RD Licensing will expose the whole domain.
      2) You won't be able to add high-availability to CB in the future.
      My template deploys DC on Basic_A1 VM, which is relatively cheap and don't increase the bill significantly.

  2. Shah says:

    How can I edit this to deploy in my existing AD Domain in azure. So to deploy the rest of the VMs, but join my existing domain.

    1. Just remove lines that are responsible for DC creation.

      1. Daniel Edwards says:

        How do we specifically know which lines are responsible for DC creation? Im trying to edit the RDS-deployment script to remove the DC creation as well as VNET creation.
        Many lines reference the variables or have "depends on" dependencies set to the AD variables.

        1. Ryan says:

          Krill, thank you for your hard work on this, this is very helpful.
          Please clarify how we can use the template to join existing domain, I believe it is more complex than simply removing the DC lines.

          1. Just remove lines that create DC (or modify them to join DC to the existing domain) and change domain name to the existing. Windows Servers will find your existing domain using DNS and join it instead of joining a new domain.

  3. Alexis says:

    Can we change for machines by those below? We would also like all machines to be GRS and SSD.

    Standard_A1_v2 : One VM with Active Directory Domain Controller and DNS roles
    Standard_A1_v2 : One VM with RD Connection Broker and RD Licensing Server
    Standard_A1_v2 : One VM with RD Gateway and RD Web Access
    Standard_A2m_v2 : Specified number of RD Session Hosts

    1. Yes, you can change the list of possible VMs sized in the JSON file.

  4. Any clue as to the approximate month-to-month cost of something like this on Azure?

    1. It depends on the size of the VMs, type of workload and number of users. The price range is very wide.

  5. Ian Sutherland says:

    What does the end infrastructure look like? I'd like to be able to put it into the cost estimator before I hit go.

Skip to main content