Post-renewal script for binding new certificate to Remote Desktop Gateway


#1

Hello, all. I’ve been using Let’s Encrypt certificates for Remote Desktop Gateway for quite some time. One thing I quickly tired of was needing to remember to bind the new certificate to the service, lest the server restart and my users get a message about no certificate being bound to the service.

I poked around WMI for a while and eventually found a method that I turned into a PowerShell script that Certify can use. I hope this helps anyone in my boat. Click that link for the full script; otherwise, here’s the quick and dirty version.

$AllLECerts = (Get-ChildItem -Path cert:\LocalMachine\My | Where-Object {$_.Issuer -Like "Let's Encrypt Authority X*"})
$NewestLECert = ($AllLECerts | Sort-Object -Descending NotAfter)[0].Thumbprint
$ByteArray = ($NewestLECert -Split "(?<=\G\w{2})(?=\w{2})" | ForEach {[Convert]::ToByte($_,16)})
$wmi = (Get-WmiObject -Class "Win32_TSGatewayServerSettings" -Namespace "root\cimv2\terminalservices")
$wmi.SetCertificate($ByteArray)
Restart-Service TSGateway

403 - Forbidden: Access is denied. on Anywhere Access
Server 2016 - MS RDP Gateway: Enable Cert
#2

Thanks for sharing!

We have a suggested script that we build into Certify (v4 alpha onwards) you can see here:

This one uses the thumbprint passed by the renewal process. Let us know if you can suggest improvements - we should probably list the restart version as a separate script as some people will want the restart and others won’t.


#3

That’s great! I didn’t know there was an RDS: drive. This would be better than mine because it takes the exact thumbprint as an argument.

Certificate changes aren’t applied until the service restarts, but that is disruptive. Maybe a popup or a warning message is in order, at least.


#4

They challenge here is that the renewal may happen automatically (that’s optional). If it did there needs to be a controlled way for the user to step in and apply the certificate. We could pause deployment and email them to let them know they need to resume the deployment manually?


#5

This is SO great to see this discussion as I have the same issue. From a time standpoint, paying $40/year for a certificate that is good for 2-3 years is cheaper than my time remotely logging onto a client’s server just to re-bind the renewed cert every 90 or so days. But if this works, this will be great.

In my case, I have the renewal set to “auto” and I must admit, I don’t know when the renewal cycle actually starts, or WHEN during the day it would run. Ideally, the task scheduler could/should be used so we could limit the timing to only “after” hours or early AM so we could be pretty sure no users would be affected when the gateway manager restarted.


#6

Thanks, I completely agree that some cert updates like this are time sensitive. While the renewal itself can happen any time, the user should ideally be able to specify the exact day/time to deploy it, either on a schedule or as manual push button step.


#7

I’ve created a new issue to track this feature: https://github.com/webprofusion/certify/issues/329


#8

This script works on most of my 2012 R2 servers, except for one running Essentials. I can find the RDS: drive from Windows PowerShell, but Certify can’t find RDS: when it calls the script. So, I’ve merged my script with yours thanks to a quick call to Test-Path. This should replace the certificate no matter what.

# Replace-RDGatewayCertificate.ps1
Param($result)

# Use the RDS PowerShell module, if available.
# If not, we'll have to set it manually via WMI.
Import-Module RemoteDesktopServices -ErrorAction SilentlyContinue
$RDSPath = "RDS:\GatewayServer\SSLCertificate\Thumbprint"
If (Test-Path $RDSPath) {
    Set-Item -Path $RDSPath -Value  $result.ManagedItem.CertificateThumbprintHash -ErrorAction Continue
} Else {
    # Convert the certificate thumbprint from a String into a Byte[] that WMI can understand.
    # The next line is courtesy of: http://www.beefycode.com/post/Convert-FromHex-PowerShell-Filter.aspx
    $ByteArray = ($result.ManagedItem.CertificateThumbprintHash -Split "(?<=\G\w{2})(?=\w{2})" | ForEach {[Convert]::ToByte($_,16)})
    $wmi = (Get-WmiObject -Class "Win32_TSGatewayServerSettings" -Namespace "root\cimv2\terminalservices")
    $wmi.SetCertificate($ByteArray)
}
# Uncomment the next cmdlet to automatically restart the RD Gateway service.
# This is required to apply the new certificate, but it will briefly disconnect all users.
#Restart-Service TSGateway -ErrorAction Continue

I can also make a pull request on GitHub to get this code change applied into that other script.


#9

I think on a default Essentials 2016 a bit more is needed before your script will work. It needs Install-WindowsFeature RSAT-RDS-Gateway because that is not installed by default.


Server 2016 - MS RDP Gateway: Enable Cert
#10

In case it helps anyone, on Windows Server 2012 Essentials (not R2) it was necessary to import both of these for it to work:

Import-Module RemoteDesktopServices
Import-Module RemoteDesktop