Configure Remote Desktop Gateway bastion hosts with PowerShell

Three years ago this month, I described how you can use the Remote Desktop Gateway (RDG) as a Windows bastion, or jump, server to provide secure access to Windows hosts inside a private subnet in either AWS and/or Azure. Here’s a picture of how this works.


Remote Desktop Gateway connections
Remote Desktop Gateway connections (click to enlarge)


[Update 2017-08-22: see this post for a soup-to-nuts PowerShell script to configure an Azure Windows jump host. It includes the code below to configure RDG in an Azure Vnet.]

Note that it’s possible for the RDG host to connect to itself. Simply specify the internal address or DNS of the RDG host as the RDP connection destination. I do this all the time as a test — of the configuration as well as my own mind as it’s a bit of a mental twister.

But there’s always been a fly in the ointment of this otherwise standard process: configuring the RDG server itself had to be done via the UI.

This major disadvantage meant that you could use DevOps tools like Chef or Azure DSC to install the Remote Desktop role on the server — but you had to log into the machine to configure RDG itself.

Recently, I decided to see if there was a way to use PowerShell to configure a basic RDG server. You’d expect that by now, given the importance of Windows jump servers to Azure and AWS IaaS deployments, Microsoft would have shipped PowerShell cmdlets to make configuration a snap.

But all I could find was a single blog post (from 2011!) describing in very general terms a PowerShell provider for Remote Desktop Services. (If you find better doc, please leave a comment.)

PowerShell providers (see about_Providers) make everything look like a filesystem. That works well for resources like the registry (HKLM:) and certificates (Cert:). But I think it’s less successful for RDG.

But the main issue is that there doesn’t appear to be any documentation of what all the options are. So, you are left to issuing

Get-ChildItem RDS:\GatewayServer -Recurse | Format-List > $HOME\Desktop\RDG.txt

to discover what can be done.

The script below creates a basic RDG. It requires that the Remote Desktop Services role has already been installed. That’s a snap with almost any DevOps tool — it’s a simple Install-WindowsFeature cmdlet. (I’ll be posting Here is an Azure DSC configuration soon showing how to do this in an Azure IaaS VM.) Here’s what it does:

  • Creates a self-signed certificate and stores it on the administrator’s desktop. This cert must be installed on client machines before RDP will connect to the RDG server
  • Creates a Connection Authorization Policy (RD-CAP) that authorizes local users in the Administrators and Remote Desktop Users groups to access the RDG
  • Creates an Resource Access Policy (RD-RAP) that allows all types of redirection (file, printer, clipboard, etc.) and permits the same two groups as in the RD-CAP to access all reachable Windows hosts on TCP 3389

You will still need to open TCP 3389 on the public interface to this RDG long enough to retrieve the cert to download and install it on your client. As soon as you do that, you can eliminate TCP 3389 from the network security group in your AWS VPC or Azure VNet. Just connect to the RDG on TCP 443 via the public interface and tell your RDP client to connect you to the RDG’s private interface. This test confirms that everything is working correctly.

        Configure a Remote Desktop Gateway jump (bastion) server
        Uses the Remote Desktop Services PowerShell provider to create/install an RD-CAP and an RD-RAP. Also creates and exports a self-signed certificate for use on connecting clients
    .PARAMETER dnsName
        FQDN of the to-be-generated self-signed certificate
        Self-signed cert at $HOME/desktop/$dnsName.cert 
        Remote Desktop Service role must exist on server before this script is run.
        This script adds non-AD local groups to RD-CAP and permits all accesses to back-end resources
        Alex Neihaus 2017-07-25
        (c) 2017 Air11 Technology LLC -- licensed under the Apache OpenSource 2.0 license,
        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at
        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.
        Author's blog:

Import-Module RemoteDesktopServices
# Create a self-signed certificate. This MUST be installed in the client's Trusted Root store for RDP clients to be able to use it
$dnsName = ""
$x509Obj = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName $dnsName
# Export the cert to the administrator's desktop for use on clients
$x509Obj | Export-Certificate -FilePath "$HOME\$dnsName.cer" -Force -Type CERT
# See
#   for details of using the RDS provider. It's very poorly documented. If you need to find additional items, sl to the GatewayServer location you are interested in
#   and gci . -recurse | fl
# Create RD-CAP with two user groups; defaults permit all device redirection. Might be worth tightening up in terms of security.
$capName = "RD-CAP-$(Get-Date -Format FileDateTimeUniversal)"
Set-Location RDS:\GatewayServer\SSLCertificate #Change to location where self-signed certificate is specified
Set-Item .\Thumbprint -Value $x509obj.Thumbprint # Update RDG with the thumprint of the self-signed cert.
# Create a new Connection Authorization Profile
New-Item -Path RDS:\GatewayServer\CAP -Name $capName -UserGroups @("administrators@BUILTIN"; "Remote Desktop Users@BUILTIN") -AuthMethod 1
# Create a new Resouce Authorization Profile with "ComputerGroupType" set to 2 to permit connections to any device
$rapName = "RD-RAP-$(Get-Date -Format FileDateTimeUniversal)"
New-Item -Path RDS:\GatewayServer\RAP -Name $rapName -UserGroups @("administrators@BUILTIN"; "Remote Desktop Users@BUILTIN") -ComputerGroupType 2
Restart-Service TSGateway # We're done; let's put everything into effect




, , , , ,



2 responses to “Configure Remote Desktop Gateway bastion hosts with PowerShell”

  1. Jeff TX Avatar
    Jeff TX

    Hi Alex,

    Here are a few additional reference sources for automating the setup of RD Gateway Servers:

    AWS Quick Start for RD Gateway Server:
    Here’s a more recent PS deployment for Win Server 2012 R2:
    And lastly, here’s a blog on licensing, which can become a challenge when trying to apply CALs for additional logins:

    1. Alex Neihaus Avatar
      Alex Neihaus

      Thanks, Jeff!

      I appreciate the additional links — hopefully people will find them useful. I still think that Microsoft has done a terrible job of documenting DevOps-style RDS implementations. And, frankly, I am not so much interested in RDS as I am in RDG.

      And that brings me to AWS: their new templates are great…but several years late. Going back to 2014, they had nothing useful. The CloudFormation templates from that era were inextricably linked to a new VPC — making them less useful for folks looking to add RDG to an existing VPC. Even today, I believe the templates you linked to leave TCP 3389 open for access to the RDG — something that’s just asking for trouble.

      Still, AWS is improving. MSFT isn’t, IMO.

Leave a Reply

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