How to Deploy your ‘Cloud-Only’ RDS environment – Part 2

Standard

In this second blogpost of the series deploying a ‘Cloud-Only’ RDS environment I want to focus on deploying all needed roles on Azure by using an Azure Resource Manager Template. After the deployment of the resources I also want to show how the deployment of the RDS environment itself can be initiated from an ARM template. Part 1 of the series contained the creation of a AzureAD with Domain Services and the VNET peering configuration between the Classic VNET (Needed for AzureAD Domain Services) and the ARM VNET used within the ARM template for the RDS Resources. The steps described in this first blogpost are required to execute the steps in this blogpost.

Let’s first talk about the use of Azure Resource Manager(ARM) templates. With ARM templates it’s possible to deploy resources on Azure, with ARM templates you can only deploy resources which are available in the Azure Resource Manager. If functionality is not available in Azure Resource Manager it cannot be deployed through an ARM Template. A good example is Azure AD Domain Services, this service need to be created through either the PowerShell classic cmdlets or the Azure Portal. More information about the Classic and Azure Resource Manager model can be found here. A Resource Manager template is written in the JavaScript Object Notation (JSON) language. With a template you can define one or more resources to deploy to a resource group. It also defines the dependencies between the deployed resources. The template can be used to deploy the resources consistently and repeatedly. More information about authoring these templates can be found here.

Microsoft, it’s partners and the community created a great set of ARM templates which can be found here: https://azure.microsoft.com/en-us/documentation/templates/. Also for RDS there are some templates available:

You can easily select one of those templates and deploy them in your own environment. But my personal goal is to create a own template and use my own scripts to deploy a RDS environment using Cloud Services where possible. The above templates are using PowerShell DSC to configure the components and install the RDS environment within the virtual machines. I’ve tested these templates and the PowerShell DSC deployments and it’s working, but I’ve my own scripts and want to use those within a Azure ARM deployment. So I’m not using the PowerShell DSC configurations and using the Azure ‘CustomScriptExtension’ extension. Let’s take a look into the steps needed to deploy the resources on Azure and deploy a basic RDS environment in those virtual machines.

  1. First edit the default policy within Azure AD domain Services to active WinRM. Detailed instructions can be found here.
  2. The second thing which we need to configure is CredSSP. CredSSP is needed because the RDS cmdlets using PowerShell Remoting to execute actions. Since I need to use PowerShell remoting to start my scripts through the ‘customscriptextension’ under a domain user the PowerShell remoting session from the RDS cmdlets are the ‘second-hop’. More detailed information about the PowerShell second-hop can be found here. Since CredSSP is not the most secure setting I’m only using it in my scripts and disable it at the end. I’ve added the configuration to my PowerShell scripts:
    Enable CredSSP (at the beginning of the script):

    Enable-WSManCredSSP -Role Client -DelegateComputer *.oneworkplace.nl -Force
    Enable-WSManCredSSP -Role Server -Force
    

    Disable CredSSP (at the end of the script):

    Disable-WSManCredSSP -Role Client
    Disable-WSManCredSSP -Role Server
    

  3. Next step is to start creating ARM templates with defining the following resources:

    The environments consists of 2 availability sets, one for the RD Connection Brokers and one for the RD Gateway / Web servers. It doesn’t make sense to create one for the RD Session Hosts. RD Session Hosts are hosting the active user sessions, making those servers part of a availability set only guarantees that the session hosts will not be rebooted together. That is not solving the issue that active user settings will survive platform issues or scheduled reboots. Beside those availability sets I’m also creating 2 internal load balancers, one for the RD Connection brokers and one for the RD Gateway / Web servers. Both roles are places internally since I want to use the Azure AD Application Proxy for publishing my environment. In the end the virtual machines will be created.

  4. All of the above resources are pretty ‘standard’ configurations which can be found here: https://github.com/Azure/azure-quickstart-templates.
  5. The AD domain join within the RDS templates from the gallery are done by the DSC extensions, since I’m not using those I’ve used the Azure ADDomainJoin extensions. The following JSON code can be added to the VM configuration:
    "resources": [
                  {
                    "apiVersion": "2015-06-15",
                    "type": "Microsoft.Compute/virtualMachines/extensions",
                    "name": "[concat(variables('vmNamePrefixGW'), copyindex(),'/joindomain')]",
                    "location": "[resourceGroup().location]",
                    "dependsOn": [
                        "[concat('Microsoft.Compute/virtualMachines/', variables('vmNamePrefixGW'), copyindex())]"
                    ],
                    "properties": {
                       "publisher": "Microsoft.Compute",
                       "type": "JsonADDomainExtension",
                       "typeHandlerVersion": "1.3",
                       "autoUpgradeMinorVersion": true,
                       "settings": {                     
                         "Name": "[parameters('DomainFQDN')]",
                         "User": "[concat(parameters('DomainJoinUserName'), '@' , parameters('DomainFQDN'))]",
                         "Restart": "true",
                         "Options": "[variables('domainJoinOptions')]"
                    },
                    "protectedsettings": {
                        "Password": "[parameters('DomainJoinUserPassword')]"
                    }
                    }
                   }
                  ]
    

    The above example uses variable and parameters which you need to defined in your ARM template. More information about variables and parameters can be found here.

  6. Now I want to dive into the last item in the list ‘ConfigureRDS’. As you probably can see based on the icon it’s a extension; in my case the ‘CustomScriptExtension’ which deploys the RDS environment. This extensions consists of the following sections:
               {
                 "name": "[concat(variables('vmNamePrefixCB'), '0/ConfigureRDSDeployment')]",
                 "type": "Microsoft.Compute/virtualMachines/extensions",
                 "location": "[resourceGroup().location]",
                 "apiVersion": "2015-06-15",
                 "dependsOn": [
                   "[concat('Microsoft.Compute/virtualMachines/', variables('vmNamePrefixGW'), '0/extensions/joindomain')]",
                   "[concat('Microsoft.Compute/virtualMachines/', variables('vmNamePrefixSH'), '0/extensions/joindomain')]",
                   "[concat('Microsoft.Compute/virtualMachines/', variables('vmNamePrefixCB'), '0/extensions/joindomain')]"
                 ],
    
    

    The above section defines the name, type, location and the resources where it depends on. Those resources need to be deployed before this extension will start. The name also defines where the extension will run. The above exentension will run on my broker server since that resource is in the name of this extension.

    "properties": {
                   "publisher": "Microsoft.Compute",
                   "type": "CustomScriptExtension",
                   "typeHandlerVersion": "1.4",
                   "autoUpgradeMinorVersion": true,
                   "settings": {
                       "fileUris": [
                       "https://raw.githubusercontent.com/arjanvroege/rdscloudonly/master/configure_rds_env.ps1"
                   ],
                   "commandToExecute": "[concat('powershell.exe -ExecutionPolicy bypass -File configure_rds_env.ps1 -FQDNDomain ', parameters('DomainFQDN'),' -Cred_User ', parameters('DomainJoinUserName'), ' -Cred_Psswd ', parameters('DomainJoinUserPassword'), ' -Pri_RDCB ', variables('vmNamePrefixCB'), '0 -Pri_RDGW ', variables('vmNamePrefixGW'), '0 -Pri_RDSH ', variables('vmNamePrefixSH'),'0')]"
                   }
                 }
    

    The above section defines the plugin itself. In the fileUris section the files involved in the extension are described, those files will be downloaded by the extension before execution. The commandToExecute consists of the command line which will be executed by the extension. This command line constructed which static text, variables and parameters. The complete extension will look like this:

    {
                 "name": "[concat(variables('vmNamePrefixCB'), '0/ConfigureRDSDeployment')]",
                 "type": "Microsoft.Compute/virtualMachines/extensions",
                 "location": "[resourceGroup().location]",
                 "apiVersion": "2015-06-15",
                 "dependsOn": [
                   "[concat('Microsoft.Compute/virtualMachines/', variables('vmNamePrefixGW'), '0/extensions/joindomain')]",
                   "[concat('Microsoft.Compute/virtualMachines/', variables('vmNamePrefixSH'), '0/extensions/joindomain')]",
                   "[concat('Microsoft.Compute/virtualMachines/', variables('vmNamePrefixCB'), '0/extensions/joindomain')]"
                 ],
                 "tags": {
                   "displayName": "ConfigureRDS"
                 },
                 "properties": {
                   "publisher": "Microsoft.Compute",
                   "type": "CustomScriptExtension",
                   "typeHandlerVersion": "1.4",
                   "autoUpgradeMinorVersion": true,
                   "settings": {
                     "fileUris": [
                       "https://raw.githubusercontent.com/arjanvroege/rdscloudonly/master/configure_rds_env.ps1"
                     ],
                     "commandToExecute": "[concat('powershell.exe -ExecutionPolicy bypass -File configure_rds_env.ps1 -FQDNDomain ', parameters('DomainFQDN'),' -Cred_User ', parameters('DomainJoinUserName'), ' -Cred_Psswd ', parameters('DomainJoinUserPassword'), ' -Pri_RDCB ', variables('vmNamePrefixCB'), '0 -Pri_RDGW ', variables('vmNamePrefixGW'), '0 -Pri_RDSH ', variables('vmNamePrefixSH'),'0')]"
                   }
                 }
              }
    
  7. The next step is to define the parameters for the template. You can add the parameters to the command line but I prefer to create a parameters file with all parameters defined in one file which I can easily edit when needed. My parameter look like this:

  8. So now we have the template and the parameters file we’re ready to test the template before deployment. This can be done by using the cmdlet ‘Test-AzureRmResourceGroupDeployment’ The complete command line will look like this:
    Test-AzureRmResourceGroupDeployment -ResourceGroupName 'RDSCloudonly-ARM' -TemplateFile 'C:\Deploy_RDS_CloudOnly\Templates\RDS2016_v01.json' -ParamatersFile 'C:\Deploy_RDS_CloudOnly\Templates\azuredeploy.parameters.json'
    
  9. If the test is succesfull we can start the deployment of the environment by execusting the following command line:
    New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) -ResourceGroupName 'RDSCloudonly-ARM' -TemplateFile 'C:\Deploy_RDS_CloudOnly\Templates\RDS2016_v01.json' -ParamatersFile 'C:\Deploy_RDS_CloudOnly\Templates\azuredeploy.parameters.json'
    

The results of a deployment will look like this:

And on the fresh installed RD Connection Broker:

As you can see with Azure ARM and custom scripts you can automate deployments of complete environments. The current solution is far from complete and I’ve a lot of ideas which I will add to the script in the coming months. But I wanted to share already the power of Azure ARM templates together with PowerShell scripts which can be run through DSC or a custom script extension. So stay tuned for updates on this script!

If you would like to see a RDS deployment based on ARM templates and PowerShell DSC please see this blogpost of a colleague RDS MVP Freek Berson >> http://microsoftplatform.blogspot.nl/2016/10/video-of-ignite-session-showing-rds-on.html.

2 thoughts on “How to Deploy your ‘Cloud-Only’ RDS environment – Part 2

  1. Eric

    Great post. In future posts, could you include how to scale down users rights and how to prevent users from accessing things like local harddrives, etc?

    • Arjan Vroege

      Hi Eric,

      Thanks for your comment. I will cover this in my 4th part of this series. Stay tuned and let me know if that post answers your questions.

      Regards, Arjan

Leave a Reply

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