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

  • Anonymous
    January 09, 2017
    The comment has been removed
    • Anonymous
      January 10, 2017
      The comment has been removed
  • Anonymous
    March 14, 2017
    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.
    • Anonymous
      March 15, 2017
      Just remove lines that are responsible for DC creation.
      • Anonymous
        May 24, 2017
        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.
        • Anonymous
          July 13, 2017
          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.
          • Anonymous
            July 13, 2017
            The comment has been removed
        • Anonymous
          October 10, 2017
          I too would love to know this - Please provide some help with this Kirill - many thanks
  • Anonymous
    May 15, 2017
    The comment has been removed
    • Anonymous
      May 24, 2017
      Yes, you can change the list of possible VMs sized in the JSON file.
  • Anonymous
    May 17, 2017
    Any clue as to the approximate month-to-month cost of something like this on Azure?
    • Anonymous
      May 24, 2017
      It depends on the size of the VMs, type of workload and number of users. The price range is very wide.
  • Anonymous
    May 23, 2017
    Looks like this is working from the link from GitHub https://github.com/Azure/azure-quickstart-templates/tree/master/rds-deployment
  • Anonymous
    May 31, 2017
    What does the end infrastructure look like? I'd like to be able to put it into the cost estimator before I hit go.
  • Anonymous
    August 03, 2017
    Hi, do you have mor info about licensing ?Greats
  • Anonymous
    March 01, 2018
    Hi Kirill,Nice template and have just used it for setting up a lab \ PoC in our Azure environment. Took the steps to create the SSL and created a Session collection for a remote desktop.Created a couple of test users in the AD and can logon to the rdweb and see the published desktop. However, when I launch the desktop and login, it logs the user in as rsadmin (as per the user in your template) and not the test user?Coming from a mainly citrix background, I have not seen this before.Any thoughts?