PowerShell – Create a fully automated RDS Farm (2016) with HA and Gateway in 25 minutes

Reading Time: 4 minutes

Im a big fan of Citrix XenApp/XenDesktop but for some small customers (20-30 user) the licensing costs are to high and there is definitely demand for application and desktop virtualization. Thats why I came up with the idea to automate the proccess to install a native Microsoft RDS Farm with High Availability and RDS Gateway to access the published resources when you are located outside the companys network. The complete procedure to create a Multi & HA site takes about 25 minutes in my lab environment.

Overview

With the PowerShell script you can create two different builds.

#Build 1  – Multi Server Deployment

  • 2x RDS Session Host
  • 1x RDS Broker
  • 1x RDS WebAccess
  • 1x RDS Gateway
  • 1x RDS Licensing

#Build 2 – HA Server Deployment

  • 2x RDS Session Host
  • 2x RDS Broker
  • 2x RDS WebAccess
  • 2x RDS Gateway
  • 1x RDS Licensing

Network Diagram

rdsoverview

Firewall Configuration

From

To

Port

Description

RDS Gateway

Domain Controller

TCP/UDP 53

DNS

RDS Gateway

Domain Controller

TCP/UDP 389

LDAP

RDS Gateway

Domain Controller

TCP 135

RPC

RDS Gateway

Domain Controller

TCP 88

Kerberos

RDS Gateway

Sesssion Host

TCP/UDP 3389

RDP

Internet

RDS Gateway

TCP 443

HTTP over SSL

Internet

RDS Gateway

UDP 3391

RDP

If you need a more detailed port usage please check this technet article.

How is it working?

You just need to download the script package and copy the stuff to “C:\rds”. This is necessary because the path to the configuration file is hardcoded in the script and some additonal files will be downloaded during the deployment.

https://github.com/citrixguyblog/PowerShellRDSDeployment

Here is a config file to create a #Build2 environment.

Important: The config file is based on json. For a “\” you always need to put “\\” for not breaking the syntax!

What do I need to know before running the script?

  1. A Wildcard SSL certificate from a 3rd Party PKI is irreplaceable. You also can implement certificates from your enterprise PKI but this can come to unaesthetic warn messages for the users when accessing the infrastructure outside the LAN because the certification revocation list is not reachable.
  2.  The user which is running the script needs to have the right to create DNS Records and Security Groups in Active Directory. If the RSAT Tools are missing they will be installed by the script.
  3. Determine the FQDNs you want to use: Broker, Gateway, WebAcces (The script will generate Split DNS records. My internal domain is “lab.local” and the wildcard certificate is issued for “*.flashmob-saulgau.de)
  4. Create an UNC-Share for the ProfileDisk. You only need to set “Read” access for “Everyone”. Everything else will be handled by the broker machines.
  5. Fill out the configuration file and rename it to “config.json”.
  6. Run the Script (Install_RDSFarm.ps1) as administrator.

HA Broker and SQL Database

Update (2018-02-17)

The script will create the SQL Login for the RDS Broker Security Group. The user running the script just needs sysadmin rights on the database server. Thanks to Sander van Gelderen for contributing the modifications.

When deploying #Build2 you need to interact during the script execution.  To create the database for the HA Broker  the computer accounts needs to have the permission to create a database on the SQL server.  The problem is that the command “Set-RDConnectionBrokerHighAvailability” will always create a new database. You can’t set the permission on an empty “RDSFarm1” database before running the script. You need to give the SecurityGroup “RDS_Connection_Brokers” temporary  the role “sysadmin” to complete the setup. Afterwards you should change the permission to “db_owner”. The script will instruct you to do this.

Load Balancing

This setup is based on DNS Round Robin. Its always better to use a hardware/software load balancer to check if the service is running properly. If you guys are interested how to load balance the components with Microsoft Network Load Balancing (NLB) and Citrix NetScaler drop me a message and I will write a blogpost about this.

How can I access the RDSFarm?

Method 1 – Native RDP Client
Connect to the RDSBroker FQDN and configure Gateway settings.

mstsc1

mstsc1_2

mstsc2

Method 2 – WebAccess (Browser)
Browse to the configured RemoteAccess FQDN. After succesfull login you can access your Desktop or Remote Apps.

web1

web2

Method 3 – WebAccess with URL or E-Mail
This will create shortcuts in the Windows Startmenu. Its also possible to set the connection URL via Group Policy. ( User –> Windows Components\Remote Desktop Services\RemoteApp and Desktop Connections –> “Specify default connection URL”)

remoteapp1

remoteapp2_1

remoteapp2

remoteapp5

36 comments

  1. great script, made some adjustments. But I have something stange… adding the seconds brokers gives me a error: Add-RDServer : The server BR2.rdsfarm.lab has to be same OS version as the active RD Connection Broker server BR1.rdsfarm.lab: Microsoft Windows Server 2016 Standard.

    And I know the versions are the same! do you know what to do?

      1. Julian, yes patch level is the same, my images are syspreped vhdx files. But I think I know what the error means, I will do some tests somewhere next week and let you know.

      2. Julian,

        Found the solution for the issue about ” Add-RDServer : The server BR2.rdsfarm.lab has to be same OS version as the active RD Connection Broker server BR1.rdsfarm.lab: Microsoft Windows Server 2016 Standard. ” Do you mind if I write about that and refer to your blog?
        I also want to do a pull request on github. I automated you’re manual actions (sorry :-D)

  2. If I want to remove the 2nd connection Broker and use Build 1 do I need to do any modifications to the powershell script. I tried removing the lines for the 2nd Connection Broker and the script now throws tons of errors.

  3. Really great work, however I did made some modifications to the script. 1. SQL database connection & logging on prechecks, 2. HA mode automatic UserProfiles folder creation (in my case to fileserver cluster). I’m still receiving a few errors (I have run script just once). I can’t create RDS:\GatewayServer\GatewayManagedComputerGroups\RDSFarm1 because I don’t have sufficient permissions? Then #Configure RDSBrokerHighAvailability fails, I guess because logging on to SQL Database failed earlier (HA mode is not available). Now I have working SQL database and I’ll re-run script. But that RDSFarm1 error remains mystery still.

      1. Yes. I tried also manually. And I also ran the script second time but no luck. This is the part that fails: New-Item -Path “RDS:\GatewayServer\GatewayManagedComputerGroups” -Name “RDSFarm1” -Description “RDSFarm1” -Computers “$RDBrokerDNSInternalName.$RDBrokerDNSInternalZone” -ItemType “String”. For the first run VM’s were fresh Azure Server 2016 VM’s. For the record I’m trying to put RD Gateway and RD Web Access roles into same VM. Two VM’s of course because I’m trying to create HA RDS setup.

  4. Yes. If you mean ADUC. I changed script quite a lot now, pre-checks mostly. Config file: https://pastebin.com/9b0A9iAE, Script: https://pastebin.com/prG64ptT. Text from Powershell window: https://pastebin.com/CesdDCCv.
    I created 2x CB, 2 GW/WA, 2 SH, 1 DC, 1 SQL. CB works as license server. I can see 1 CB, 2 SH’s and 1 GW/WA from deployment overview. Calculator opened just fine. DNSRR looks good. There is RDS_Connection_Brokers group on SQL DB with sysadmin permissions. Both CB’s are on that group. That group doesn’t have permissions on „Broker“ database, which should be db for CB HA.
    On that text file there is a mention: „Set-RDConnectionBrokerHighAvailability : A deployment is not present.“ I guess that’s why CB HA doesn’t work. There could be some check…
    Don’t know why GW/WA is not installed correctly, IIS and tools related to IIS are not installed on GW2.
    Hopefully I can rollback with these Hyper-V checkpoints… because I wouldn’t want to install all the servers again. 🙂

  5. Trying to do a Hyper-V VM creation and installation for this scripts. copy and paste most if not 98% from @xenappblog Erick example PS script.
    [code]

    #
    # Build #2 – HA Server Deployment – https://citrixguyblog.com/2017/12/05/powershell-create-a-fully-automated-rds-farm-2016-with-ha-and-gateway-in-25-minutes/
    #
    Set-ExecutionPolicy bypass -Force

    # Global Settings from here on.
    $VMLoc = "C:\Lab\VHDs"
    $VMGen = "2"
    $vCPU = "2"

    # Firewall
    $VMName = "FW-01"
    $Network = "External"
    $Network2 = "Internal"
    $MAC = "00:15:5D:1D:17:02"
    $MAC2 = "00:15:5D:1D:17:01"
    $ISO = "C:\Lab\ISOs\vyos118.iso"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 512MB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 5GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName
    Add-VMNetworkAdapter -StaticMacAddress $MAC2 -VMName $VMName -SwitchName $Network2

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # Global Settings from here on.
    $Network = "Internal"
    $ISO = "C:\Lab\ISOs\Win2019.iso"

    # DC-01
    $VMName = "DC-01"
    $MAC = "00:15:5D:1D:18:01"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 100GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # PS-01 – print server
    $VMName = "PS-01"
    $MAC = "00:15:5D:1D:18:02"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 40GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # FS-01 – file server
    $VMName = "FS-01"
    $MAC = "00:15:5D:1D:18:03"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 100GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # SQL-01
    $VMName = "SQL-01"
    $MAC = "00:15:5D:1D:18:04"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 80GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # 2x RDS Session Host

    # RDSHost-01
    $VMName = "RDSHost-01"
    $MAC = "00:15:5D:1D:19:01"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 80GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # RDSHost-02
    $VMName = "RDSHost-02"
    $MAC = "00:15:5D:1D:19:02"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 80GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # 2x RDS Broker

    # RDSBroker-01
    $VMName = "RDSBroker-01"
    $MAC = "00:15:5D:1D:19:03"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 60GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # RDSBroker-02
    $VMName = "RDSBroker-02"
    $MAC = "00:15:5D:1D:19:04"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 60GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # 2x RDS WebAccess

    # RDSWeb-01
    $VMName = "RDSWeb-01"
    $MAC = "00:15:5D:1D:19:05"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 40GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # RDSWeb-02
    $VMName = "RDSWeb-02"
    $MAC = "00:15:5D:1D:19:06"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 40GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # 2x RDS Gateway

    # RDSGW-01
    $VMName = "RDSGW-01"
    $MAC = "00:15:5D:1D:19:07"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 40GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # RDSGW-02
    $VMName = "RDSGW-02"
    $MAC = "00:15:5D:1D:19:08"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 40GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    # 1x RDS Licensing

    # RDSLic-01
    $VMName = "RDSLic-01"
    $MAC = "00:15:5D:1D:19:09"

    # Create Virtual Machines
    New-VM -Name $VMName -Path $VMLoc -Generation $VMGen -MemoryStartupBytes 2GB -NewVHDPath $VMLOC\$VMName.vhdx -NewVHDSizeBytes 40GB -SwitchName $Network
    Set-VMNetworkAdapter -StaticMacAddress $MAC -VMName $VMName

    # Configure Virtual Machines
    Add-VMDvdDrive -VMName $VMName -Path $ISO
    $DVD = Get-VMDvdDrive -VMName $VMName
    Set-VMFirmware -VMName $VMName -FirstBootDevice $DVD
    Set-VMProcessor -VMName $VMName -Count $vCPU
    Set-VMFirmware -VMName $VMName -EnableSecureBoot Off

    [/code]

        1. That’s not the scope of this article. If you want to deploy the core services like domain controller etc. you need to script that on your own. You already found the Automation Framework from Eric, there should be something included for automating core services as well 👍🏽

    1. I would suggest you to try load balancing with a Citrix NetScaler VPX Freemium. It is limited to 20Mbit and you can use it as an reverse proxy for the Gateway/WebAccess Server in the DMZ.

  6. Thank you for this excellent guide

    How can I change with script the TCP Port for the gateway from 443 to 8443 for example ?

    I only saw guides with GUI and no script. The only command i saw is set-RDSessionCollectionConfiguration but it is just for change the content for the rdp file.

    Thx

    1. I think this should be configurable under „RDS:\GatewayServer“ or with a registry key and a restart of the gateway service. If you can’t find it drop me a message and I will look for it next week ✌🏼

      1. Thank you for your quick reply.

        I found the key HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\TerminalServerGateway\Config\Core\HttpsPort. It needs to configure the firewall manually too.

        But i don’t really like to change registry keys manually 😉

        I hoped a Cmdlet could do this whole config for me.

  7. Hi Citrixguyblog,

    Thanks for sharing this script, it saved me a lot of time! Right now i’ am testing this script but it is not working like it should be. Maybe I did something wrong, can I send the output of the error log to you for any help?
    Thanks in advance!

    Kind regards,

    D

  8. Hi Citrixguyblog,
    Thanks for sharing this script, it saved me a lot of time! Right now i’ am testing this script but it is not working like it should be. Maybe I did something wrong, can I send the output of the error log to you for any help?
    Thanks in advance!
    Kind regards,
    D

  9. Hi, I’m really liking this script thank you but having an issue when running it – I assume it’s a permissions issue somewhere but am struggling to find it – perhaps you can help?

    The following servers did not validate successfully.
    RDGW1.RDSFARM.LAB
    At C:\rds\Install_RDSFarm.ps1:170 char:5
    + Add-RDServer -Server $config.RDGatewayServer01 -Role “RDS-GATEWAY …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Add-RDServer
    Add-RDServer : The server is joined to the deployment as a RD Gateway server.
    The following servers did not validate successfully.
    RDGW1.RDSFARM.LAB
    At C:\rds\Install_RDSFarm.ps1:170 char:5
    + Add-RDServer -Server $config.RDGatewayServer01 -Role “RDS-GATEWAY …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Add-RDServer

    Thank you 🙂

  10. Great script. Unfortunately I am having some issue trying to run it. I deploy RDS environments, sometimes with 1 connection broker, 1 RDS host etc or sometimes with 1 Broker and 2 RDS servers. Currently Im testing it with 1 Broker, 1 RDS but get the following error.
    WARNING: PSRemoting is not enabled on all hosts, MultiDeployment is not ready!
    I have tested using Enter-PSSession -ComputerName RDS01 and Enter-PSSession -ComputerName localhost and it is working .

    I would love to get this working as it would enable me to deploy RDS environments without mistakes. Any help would be appreciated

Leave a Reply

Your email address will not be published. Required fields are marked *