Tuesday, January 17, 2017

Creating a 100 RDSH Server RDS Deployment in Azure IaaS using ARM/JSON

If you have been following this blog you will have noticed the series on using Azure Resource Manager to perform an automated deployment of Remote Desktop Services in Azure IaaS. If not, here are links to the previous eight articles to get you up to speed: article1, article2, article3, article4, article5, article6, article7, article8.
clip_image002This article continues the story, but this time it’s different. In my continuous mission on improving the JSON script and sharing my journey here, I decided to take the JSON script to another level and push it to deploy a 100 RDSH Server deployment in Azure IaaS. And so I did. Along the way, I sacrificed 140 EUR worth of MSDN Azure Credits to accomplish this, but it was worth the fun! In this article I would like to share the outcome and also share the issues and limitations I ran into.

Getting started
If you have been reading my other articles in this series, you’ll remember that part the JSON script I discussed in there touches on the ‘Copy’ parameter and corresponding ‘CopyIndex()’ function. This is key to deploying multiple Azure Resources of the same type in a fast way. To reiterate on that, the Copy parameter in an Azure Resource is used to specify that you want ARM to deploy an Azure Resource multiple times. As per example below, we use the Copy parameter to deploy an RDSH server in Azure IaaS multiple times by specifying a value in the parameter numberOfRdshInstances.
"copy": {
  "name": "[concat(parameters('RDSHHostNamePrefix'),'vm-loop')]",
  "count": "[parameters('numberOfRdshInstances')]"
And throughout the rest of the declaration of the Azure Resource, we can use the function ‘CopyIndex()’ to refer to the specific created resource. To provide a few example of that, below we use the ‘CopyIndex()’ function to create a unique name for the Virtual Machine.
"name": "[concat(parameters('RDSHHostNamePrefix'),'0', copyindex(1))]",
And to create a matching Hostname Virtual Machine.
"computerName": "[concat(parameters('RDSHHostNamePrefix'),'0', copyindex(1))]",
And also to create a matching reference in the depends on section, pointing to the connected Virtual Network Adapter.
"[concat('Microsoft.Network/networkInterfaces/', parameters('RDSHHostNamePrefix'),'0', copyindex(1), parameters('NetworkAdapterNamePostFix'))]"
Based on the information above the first step would be to provide the parameter 'numberOfRdshInstances' with the desired number. In this case I performed this by simply modifying the corresponding this parameter in the azuredeploy.parameters.json file to 100.

If we would then kick of this deployment in an average Azure Subscription, it will most likely fail. In most cases the error would be something similar to below.

Operation results in exceeding quota limits of Core. Maximum allowed: 40, Current in use: 5, additional requested 208This is because Azure sets limitations to various resources to prevent you from accidentially deploying resources you did not intend to, leading to what they call a ‘billshock’.

The additionally requested number of 208 cores is the sum of:
- 2 RD Connection Broker Servers of type Standard_D2 (2 Cores)
- 2 RD Gateway / Web Access Servers Standard_D2 (2 Cores)
- 100 RD Session Host Servers Standard_D2 (2 Cores)

The steps to work arround this limitation are easy. Log on to the Azure Portal, and click on ‘New support request’

Follow the steps and create a new support request to raise the Core limit. In this case I chose the safe side and created a request to raise the limit to 250 cores.

In most cases the limit will be raised within 24 hours, and you’ll receive a notification from Microsoft Support to confirm this as shown below.

Now that we have 250 cores available for D-series VM’s we can kick off the deployment. On a side note, keep in mind that the subnet you select to use has enough free DHCP IP addresses. Or, in case you’re using fixed IP adresses like I do in my script, make sure you use a starting IP that results in enough space to host 100 IP addresses. The way I approached this is by starting with .60 as last octet of the IP address of the first RDSH NIC and then using the CopyIndex() function to create unique IP addresses ending with .61, .62 et cetera. The code below shows how this works.
"ipConfigurations": [
    "name": "[parameters('NetworkAdapterIPConfigName')]",
    "properties": {
      "privateIPAllocationMethod": "[parameters('NetworkAdapterIPAllocationMethod')]",
      "PrivateIpAddress": "[concat(parameters('InternalIPAddressPrefix/24'),`
    "subnet": {
      "id": "[variables('subnet-id')]"

Kicking off the deployment
At this stage we’re ready to kick the tires of this deployment for the first time. Running a 100+ Server deployment from ARM is a phenomenal sight. Literally within seconds of the launch of the deployment hundreds or resources are being created in Azure. Below is what the first few seconds looked like from the Visual Studio output.clip_image012

Within a minute, 100 Virtual Network Adapters were created and Virtual Machines were
pinning up.

And soon after the Virtual Machines were created, each would run their own Custom Extension to add itself to the existing Active Directory Domain. In the screenshot below, 81 RDSH Server have already been added.

So far so good. A limitation I then ran into however, was inside the process of creating the RDS deployment itself. If you remember from previous articles, we use a Custom Script Extension that launches a PowerShell script on the RD Connection Broker server. This script is, amongst other things, responsible for creating the initial RDS deployment and adding the RDSH Servers to that deployment. Below is the sniped of that PowerShell script where the RDS deployment is created.
#Create the basic RDS Deployment
$scriptreateBasicDeployment = {
    param($ConnectionBroker1, $Gateway1, $SessionHost1)
    import-module RemoteDesktop
    New-RDSessionDeployment -ConnectionBroker $ConnectionBroker1 -WebAccessServer $Gateway1 -SessionHost $SessionHosts

The CmdLet New-RDSessionDeployment accepts a parameter called ‘SessionHost’. This parameter allows you to specify a single RD Session Host server or, as in my example, an array of multiple RD Session Host servers. In this case, the script constructed an array consisting of 100 RD Session Host servers. It is at this point where I ran into a limitation. The New-RDSessionDeployment did not like the fact that I passed an array of 100 servers. The CmdLet seemed to hang and did not output anything. I left it running for about an hour and then decided to cancel the deployment and redesign this part of the script.

I now tried a different approach. Instead of passing an array of 100 servers in the New-RDSessionDeployment, I passed the first RDSH server only. Followed by that statement I used a for..each loop holding the Add-RDServer Cmdlet that can be used to add a single RDSH Server to an existing deployment.
Add-RDServer -Server $SessionHost -Role RDS-RD-SERVER -ConnectionBroker $ConnectionBroker

And although this approach worked, the process was very slow. On average, the Add-RDServer Cmdlet seemed to take ~2 minutes which would result in >200 minutes to complete this process.

I now took the Add-RDServer out of the script again, and instead of running that command sequential for each RD Session Host from the RD Connection Broker Custom Extension, I created a Custom Extension for the RD Session Host servers. The result is that each RD Session Host server would then add itself to the existing deployment and Session Collection, a far more effective approach.

Upon rerunning the deployment again (while also watching the Azure Credits go down fast) this approach seemed to be working really well! RD Session Host servers started adding themselves relatively quickly. This time it completed successfully!

Below is a screenshot of the deployment in process where each RDSH Server runs its own Custom Extension.

The end result is an RDS Deployment consisting of 108 RDS server rolesclip_image020

With 100 RDSH Servers into a single Session Collection.

This concludes the journey. The end conclusion is that yes you can create a 100 RDSH server automated RDS deployment using JSON and ARM. Although it’s most likely not a very common scenario, it was interesting to see what challenges and limitation we would face deploying that many servers in an automated way and a fun exercise. Being able to provision this many servers in such a short period of time really is one of the many powers behind Microsoft Azure. It truly is a very powerful and flexible Cloud platform.

Tuesday, January 3, 2017

Azure Resource Manager and JSON templates to deploy RDS in Azure IaaS – Part 8 Defender & BGinfo

This article is part 8 in a series of articles on deploying RDS in Azure IaaS using ARM & JSON Templates. Here is a quick overview of previous articles on this topic.

1. Full HA RDS 2016 deployment in Azure IaaS in < 30 minutes, Azure Resource Manager
2. RDS on Azure IaaS using ARM & JSON part 2 – demo at Microsoft Ignite!
3. Video of Ignite session showing RDS on Azure IaaS deployment using ARM/JSON
4. Windows Server 2016 GA available in Azure! – used it to deploy RDS on Azure IaaS!
5. Azure Resource Manager and JSON templates to deploy RDS in Azure IaaS – Part 5
6. Azure Resource Manager and JSON templates to deploy RDS in Azure IaaS – Part 6 RD

7. Azure Resource Manager and JSON templates to deploy RDS in Azure IaaS – Part 7 RD Web Access customization

In this part of the series, we’ll add both Microsoft Antimalware for Azure Virtual Machines (Defender) and BGInfo to the deployment.

If you’re not familiar with one of these tools, here is a brief introduction.

What is Microsoft Antimalware for Azure Virtual Machines?
Microsoft Antimalware for Azure Virtual Machines is a real-time protection capability that helps identify and remove viruses, spyware, and other malicious software, with configurable alerts when known malicious or unwanted software attempts to install itself or run on your system.

What is BGInfo?
BGinfo is a small but very powerful Sysinternals tool that automatically displays relevant information about a Windows computer on the desktop's background, such as the computer name, IP address, service pack version, and more.

Using ARM to deploy Microsoft Antimalware for Azure Virtual Machines (Defender)Let’s start with adding Microsoft Antimalware for Azure Virtual Machines, which we’ll refer to as Defender in this blog post. When running a Virtual Machine in Azure IaaS, Defender can be installed as an extension on top of a Virtual Machine, including specific exclusions you might want to have.

To easily add Defender to Virtual Machines that we’re running for our RDS deployment we’re using the extension called IaaSAntimalware. The JSON code to add the Defender extension to our VM’s looks like below. Basically we create a new VirtualMachine Extension and set ‘IaaSAntimalware’ as the type. Using the settings section, we can then further define the custom settings. In this case we define whether or not real time protection is enabled, and what type of periodic scan is performed. Furthermore, we define the file type extensions, paths and processes we want Defender to exclude. clip_image004

To be able to define different exclusions for the different RDS roles, we define separate exclusion parameters for each role type. To accomplish this, we introduce the following parameters for each role type.

And the following parameters are shared across all role types to define the scan schedules.

When running the ARM template, we can define all of the parameters to customize the usage of Defender to our needs. By default, the periodic scan is scheduled weekly at midnight performing a quick scan but this can obviously be modified at will.

As you can see we can also enable or disable real time protection per role. And we can configure a semicolon separate list of exclusions to exclude paths, processes en file extentions. The exclusions are important to configure to not run into any unnecessary performance issues after deploying Defender. For example, for the RD Gateway role I added the following exclusions, a mix of common exclusions like the logs & databases of the SoftwareDistribution folder, eventlogs and IIS log files. %windir%\SoftwareDistribution\Datastore\Datastore.edb%windir%\SoftwareDistribution\Datastore\Logs\Res*.log%windir%\SoftwareDistribution\Datastore\Logs\Edb*.jrs%windir%\SoftwareDistribution\Datastore\Logs\Edb.chk%windir%\SoftwareDistribution\Datastore\Logs\Tmp.edb%windir%\Security\Database\*.edb%windir%\Security\Database\*.sdb%windir%\Security\Database\*.log%windir%\Security\Database\*.chk%windir%\Security\Database\*.jrs%allusersprofile%\NTUser.pol%Systemroot%\System32\GroupPolicy\Registry.polC:\inetpub\logs\LogFiles\W3SVC1\*.log%SystemRoot%\System32\Winevt\Logs\*.evtx%SystemRoot%\SYSTEM32\Logfiles\*.logFor the RD Session Host role, it’s also important to take a close look at exclusion, especially because these are the servers that will host active user sessions. Common exclusions for the RD Session Host role are e.g. the Printer Spooler, the winlogon process, etc.

After completion, the Azure Portal shows a defender extension object per virtual machine.clip_image012

And when logging on to one of the created Virtual Machines, in this example one of the RD Gateway Servers, we can see that Defender is running with real time protection enabled.clip_image014

And we can confirm that the exclusions we defined in our JSON parameters files are correctly configured as well.

Using ARM to deploy Sysinternals BGInfoLet’s now look at the second addition in this blog post, BGInfo. If you’re managing multiple different servers for your organization or maybe even for multiple organizations, I’m sure you’re familiar with BGInfo. BGInfo allows you to display details like IP addresses, hostname, bootime etc. about the Virtual Machine you’re currently connected to. It’s a great tool that has been out there for many years. Of course you can manually download and install the BGInfo tool on all your servers, but since we’re doing an entire deployment based on Azure Resource Manager, let’s use ARM for this deployment as well.

Installing BGInfo from ARM is actually much easier than you might expect. There is a BGInfo Extension that you can directly reference from ARM. You create a new resource of type extentions and provide ‘BGInfo’ in the type in the properties. We do this for each of the loops of Virtual Machines we’re creating (hence the copyindex function in the name) and that’s basically it.

Alter completion the Azure Portal shows the various BGInfo resources.clip_image020

And when logging in on one of the servers we can see the result, BGInfo is there!

ARM Extensions like Defender and BgInfo add even more power to Virtual Machines running in Azure IaaS. These are just 2 examples of extensions that I thought would make sense to add to existing RDS deployment, but there are many more out there.

This concludes part 8 in a series of articles on deploying RDS in Azure IaaS using ARM & JSON Templates.

Thursday, November 24, 2016

Azure Resource Manager and JSON templates to deploy RDS in Azure IaaS – Part 7 RD Web Access customization

This article is part 7 in a series of articles on deploying RDS in Azure IaaS using ARM & JSON Templates. Here is a quick overview of previous articles on this topic.

1. Full HA RDS 2016 deployment in Azure IaaS in < 30 minutes, Azure Resource Manager
2. RDS on Azure IaaS using ARM & JSON part 2 – demo at Microsoft Ignite!
3. Video of Ignite session showing RDS on Azure IaaS deployment using ARM/JSON
4. Windows Server 2016 GA available in Azure! – used it to deploy RDS on Azure IaaS!
5. Azure Resource Manager and JSON templates to deploy RDS in Azure IaaS – Part 5
6. Azure Resource Manager and JSON templates to deploy RDS in Azure IaaS – Part 6 RD Gateway

In this part of the series, we’ll take a closer look at customizing and branding RD Web Access a bit further. If you’ve read previous articles you’ll know that part of this ARM deployment for RDS is, where we actually configure resources in Azure, based on constructing JSON templates. They allow you to configure a template that ARM can use to deploy resources in Azure. The first article in the list above explains this in more detail. The other part of this ARM deployment for RDS is done using PowerShell scripts. I use PowerShell to perform configurations within the Virtual Machines for example to create RDS Deployments, configure SSL, configure RD Gateway etc. For the branding of RD Web Access, we use the same approach, we define a Custom Extension in JSON to call a PowerShell script.

Before we dive into JSON & PowerShell. Let’s take a look at a few common customizations and branding changes in production environments.

Some of the common changes on this logon page are

1. Changing the default logo
2. Changing the Work Spaces title
3. Changing the RemoteApp and Desktop Connection titleclip_image002

4. Another common change is to redirect all requests directly to the /rdweb folder. This is used to allow users to browse to the RD Web Access page without having to specify the /rdweb in the URL. For example, users that browse to https://rds.rdsgurus.com are automatically redirected to https://rds.rdsgurus.com/rdweb.clip_image004

5. A somewhat hidden feature of RD Web Access is the option to allow users to change their passwords using the RD Web Access page. This can be used to enable users to change an expired password or change a password at will if they still now the current password. For more details on this feature also this page. It was actually already possible to enable this password change in Windows Server 2008 R2

6. The last common change I want to cover in this article is hiding the “Connect to a Remote PC”. As shown below this option allows you to specify the destination server/client you want to connect to. In most cases you don’t want to confuse users with this option and simply hide it.

Now that we defined these 6 common customizations, let’s now take a look at how we can use ARM/JSON to include these customizations in our existing deployment. Similar to what we did in previous articles we’re building this on top of what we build in previous articles.

To accommodate the 6 customizations, a few new parameters are introduced in JSON.

RDWALogoSourceLocation defines the source location where the desired logo is available. By default, this logo is cropped to 47x47, by editing the necessary actual .aspx files, this can optionally also be changed.

RDWorkspaceName defines the name of the workspace title that appears in RD Web Access as well as inside RemoteApp and Desktop Connection (RADC).

RedirectRDWAToRDWeb is used to define whether automatic redirection to /rdweb should occur or not.

RDWAPasswordChangeEnabled is used to enable or disable the password change option. By default, this option is disabled.

RDWAHideDesktopsTab specifies whether or not to hide the Connect to a Remote PC option. By default, this option is disabled.

RDWAHeadingApplicationname defines the name underneath the Work Resources title.

So how are JSON Templates and PowerShell extensions connected again? Similar to previous articles we use the extension of type CustomScriptExtension. This allows us to specify a PowerShell Script to run on the deployed Virtual Machines (in this case the servers running the RD Web Access role).
The parameters as specified above are passed to this PowerShell script. It’s this PowerShell script that performs the customization & branding of RD Web Access. In my scenario I’m running the RD Web Access role on the same server that’s also running the RD Gateway role, hence the naming convention “RDGW” in the screenshot. In fact, I’m reusing the same extension which was also discussed in a previous article covering the customization of the RD Gateway role.

Let’s dill down and take a look at some snippets of the code to see how the 6 customizations we defined are being performed.

The command below is relatively simple. It copies the logo from the location specified in ARM, to the default location of RD Web Access. The key thing here though, is that you need to impersonate a user with enough permissions to be able to perform the copy command.

The next command is used to set the Workspace name, the CmdLet Set-RDWorkspace is part of the PowerShell module called RemoteDesktop and simply allows you to provide a new name for the workspace.

To configure IIS to perform an automatic redirection to the /rdweb folder. The CmdLet Set-WebConfiguration is part of a PowerShell module called WebAdministration.

To enable the change password option of RD Web Access the command below is used. The CmdLet Set-WebConfigurationProperty is also part of the PowerShell module WebAdministration.

To hide the “Connect to a Remote PC” tab in RD Web Access we’re using the same Set-WebConfigurationProperty

And finally, to change the RemoteApp and Desktop Connection title in RD Web Access we need to modify the RDWAStrings.xml which by default is located in C:\Windows\Web\RDWeb\Pages\en-US\. This file also contains other text strings that are used in RD Web Access that you can modify in a similar way.clip_image020

The end result of these customizations is shown in the screenshots below.

The logon page of RD Web Access

Connect to a Remote PC option is removed

The redirection in IIS is configured

And the password reset page is available

We are of course not limited to the 6 examples of RD Web Access branding and customization as outlined in this article. There are many more items I can think of to even further customize the end user’s experiences when accessing RD Web Access like i.e. custom style sheets, language etc. With this article however I wanted to provide some more insights on how to perform various types of customizations. Many other customizations can be performed based on the variety of methods explained in this article. If you have questions or suggestions for other customizations, feel free to contact me.

Thursday, November 17, 2016

Microsoft ARM template to deploy scale-out file server for RDS User Profile Disks (UPD)

imageMicrosoft has recently released an Azure Resource Manager (ARM) template to create a domain-joined scale-out file server (SOFS) in Azure using Storage Spaces Direct (S2D) with Windows Server 2016. You can use this template to create a Storage Spaces Direct scale-out file server to store User Profile Disks (UPD)!

”…Remote Desktop Services (RDS) requires a domain-joined file server for user profile disks (UPDs). To deploy a high availability domain-joined scale-out file server (SOFS) in Azure, use Storage Spaces Direct (S2D) with Windows Server 2016…”

Check out this link for guidance on how to set up the Storage Spaces Direct scale-out file server and how to configure UPD on it!

Thursday, November 3, 2016

Using Azure AD Domain Services for RDS deployments, limitations & caveats

The innovations on Remote Desktop Services in Windows Server 2016 are divided into three main areas. The first area, Increased Performance, is all about the graphics improvements, Discrete Device Assignment (DDA), the ability to use the GPU in session based scenario’s including the N-series VM’s in Azure. The second area is Enhanced Scale which focusses on improving the RD Connection Broker performance to now support 10K+ concurrent connection requests during log on storm scenarios. The third area is optimization for the Cloud to support a more efficient and secure architecture for Cloud deployments for example based on Azure Infrastructure as a Service (IaaS).clip_image002

Optimized for the Cloud
As part of the Optimized for the Cloud area, it is now supported to use Azure SQL Database to store the RD Connection Broker database instead of a SQL Server instance running on Azure IaaS. This is a great improvement because it avoids having to setup, maintain a SQL Server Cluster or HA group in Azure IaaS. You can now simple leverage the Azure SQL Service and choose the desired performance for that database. Also in this area is the ability to use Azure AD App Proxy to remove external endpoints on the RD Gateway enabling the RD Connection Broker, RD Licensing and RD Web Access to be combined into one VM if needed. Another improvement in the Optimized for the Cloud area is the ability to leverage Azure Active Directory Domain Services (AAD DS) in an RDS on Azure IaaS scenario. That is what this blog post is about. The diagram below highlights this component.clip_image004

Azure Active Directory Domain Services for RDS on Azure IaaS
Azure Active Directory Domain Services (AAD DS) was recently only in preview, but is now General Available. If you needed Active Directory Domain Service in Azure before AAD DS, it required setting up domain controllers in Azure IaaS, or domain controllers on premises with a VPN or Express Route connection to Azure. AAD DS extends the functionality of Azure AD to enable Domain Services functionality without having to setup Domain Controllers. Some examples of those functionalities;

- Servers in Azure IaaS to join an active directory domain
- Apply Group Policy
- Active Directory Administration Center to manage the Domain Services
- Kerberos and NTLM

So what does that mean for RDS deployments in Azure IaaS? The diagram below is included in many Microsoft presentations that discuss the improvements of Windows Server 2016, including some sessions at Ignite 2016. Also see Ignite Recap: Remote Desktop Services

What this means is that using AAD DS for RDS on Azure Azure IaaS we don’t have to setup and main those 2 Domain Controllers, but instead leverage PaaS Services. Although this sounds great and could help companies to perform a complete “lift ‘n shift” to the cloud, there are various things you need to be aware of when using AAD DS. AAD DS does not offer you the same functionality that AD DS does. In this blog post I want to cover these limitations and caveats specifically in regards to RDS environments.

Setting up AAD DS
Before we dive into the limitations caveats, let’s briefly discuss how to setup and enable AAD DS. I will only highlight the steps here, but if you need more guidance I can advise reading this guide: Administer an Azure Active Directory Domain Services managed domain.

First we need to setup an AAD, this can be done using the Azure Portal or PowerShell. Optionally you can of course also add you own custom domain, but to keep things simple for this lab I’m sticking with the onmicrosoft.com subdomain.

The next step is to simple enable AAD DS, provide the DNS Domain Name of the Domain Service and specify the Virtual Network to connect AAD DS with. This process takes a few minutes to complete.

Upon completion we can now deploy new Virtual Machines inside the Virtual Network that we specified and join those servers directly to AAD DS.

And after a reboot of this VM it is now joined to our AAD DS. You can imagine we could now create several VM’s running all RDS roles and basically setup a complete RDS environment in Azure IaaS (optionally using ARM).

Limitations & caveats when using AAD DS for RDS
With the information above as a starting point, what are some of the (current) limitations & caveats? There is a good Microsoft article discussing the differences in general called Compare Azure AD Domain Services to DIY AD domain in Azure which also contain the comparison table as shown below.

To be able to manage the AAD DS, we can use the Active Directory Administrative Center (ADAC) which is available after installing the RSAT tools.

ADAC is integrated in Server Manager and allows basic Domain Services management over AAD DS. It has a similar structure as the Active Directory Users & Computers (ADUC) console.

New Computer Object all end up in the default OU AADC Computers. You can of course create an OU structure and move those objects to a custom OU structure as shown below.

Or similar to ADDS, use PowerShell to add the computer directly into the designated OU. clip_image022
If you are deploying this using ARM/JSON check out this blog post that also covers adding servers in a designated OU using the JsonADDomainExtension.

The traditional ADUC can also still be used and we’re actually able to see the two Domain Controllers that are part of this PaaS service.

Although many of the options inside the ADUC are inaccessible and ADAC has limited functionality, it does allow us to create an OU structure and have all machines running RDS roles inside those designated OU’s and create OU structure for our users as well.

So far so good.

When taking a closer look at potentially using AAD DS for RDS on Azure IaaS however, my first response was, What about Group Policy? In probably any production environment, Group Policy Objects are needed to perform customizations, lock down user sessions and optionally to make sure certain RD Session Host settings are configured in the same way. The good news is; basic GPO management is possible. The bad news, this is very limited. Let’s summarize some of the current limitations.

1. You cannot create Custom GPO’s; the option is inaccessibleclip_image025

2. You can only modify the existing two GPO’s AADDC Users GPO and AADDC Computers GPO to configure User- and Computer settings respectively.

3. You cannot use WMI

4. You cannot use security filtering / user targetingclip_image029

5. You cannot modify the Delegation settings

So what does this all mean? Since you cannot create new GPO’s you’re stuck with a single Group Policy Object for user settings, and a single Group Policy Object for computer settings. Combine that with the fact that WMI and security filtering are not possible. This basically means that you apply the same set of GPO settings for every user and every Server. With the exception of Item Level Targeting for Group Policy Preferences still being available, but that does not cover every GPO setting. This is important to realize before you move an on premises RDS environment you might have, to Azure IaaS with AAD DS. If you made use of GPO in such an on premises environment there is a good chance you will not be able to build the same configuration in an AAD DS scenario.

To provide some examples of common RDS configurations in an AD DS that will not work in an AAD DS scenario;

- Apply a specific application setting to a specific set of users. If you have for example a Microsoft Office setting that you only want to apply to users of the finance group using WMI or security filtering, that won’t work. This is a very common scenario in RDS environments.

- Enforcing a specific computer setting to a specific Session Collection. For example, if you have multiple Session Collections because the RD Session Host servers in those collections contain different applications, you cannot apply different computer settings to the RD Session Host servers in those collections.

- Create multiple GPO’s that serve different purposes. It’s not uncommon that a User OU or a Computer OU contains multiple GPO’s that each serve a very specific set of settings. This is not possible in the AAD DS scenario.

Some of the limitations mentioned above can be solved by applying workarounds like;
- Merging all setting in a single GPO
- Relying fully on GPO Preferences & item level targets
- Moving all user specific configuration settings to login scripts
- Adding specific session collection (computer) settings inside the RD Session Host template and use that template to deploy your RD Session Host servers.
- Using 3rd party tools to completely replace GPO configurations

Although a combination of some of the workarounds specified above could allow you to create the same configuration on AAD DS as you had in AD DS, the question is if all of these workarounds and limitations are worth the effort. The costs you will save by using AAD DS instead of AD DS might not weigh up against the workarounds needed. As one of the workarounds above mentions, you can of course use 3rd party tools to completely replace GPO configurations. In those scenarios however, you will most likely be dealing with larger environments where ADDS is already in place and where the costs of deploying 2 domain controllers in IaaS is no issue at all. Personally, if I would be the one having to manage an RDS environment with these limitations, I would not be a happy admin.

I definitely see the benefit of not having to setup, manage, maintain and pay for 2 Domain Controllers in IaaS for an RDS environment. And I think there are many examples where using a PaaS service adds great value, using Azure SQL for Connection Broker is definitely one of those. When it comes to using AAD DS for RDS however, there might be user cases where for example there is a small number of applications or less need for lock down of user sessions. In those scenarios it could make sense. However, I think that with the current limitations the number of use cases where it will be successful will be very limited. It is important to mention though that AAD DS was just recently made generally available, so we can expect updates to this service that might remove some of the current limitations as discussed in this article.