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

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
2 Likes

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.

1 Like

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.

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?

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.

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.

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

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.

1 Like

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.

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

On my Server 2011 Essentials, I was SO, SO happy to see the certificate had been re-bound to the gateway manager when I verified this morning. However, it needed the gateway restart. Looking at the script I un-commented out the line. Thanks for putting it there! FWIW, I am using 4.08.

I can see why you wouldn’t want to restart the service, but once you have re-bound the cert, I doubt any connections would continue working, so I would think you would want to release the PS script WITH the restart enabled, rather than commented out.

As for the rnmixon comment above, is there any reason you wouldn’t want to include the import-modules for the script. I don’t think it would hurt in cases where the module definitions are already loaded, but would enable the script to work more out of the box.

Thanks again to every involved with this. I was beginning to wonder if I should just resume paying the $40/year…

1 Like

Thanks, although we have the script for others to use/edit we don’t currently have a test environment for Exchange or RDP Gateway (nor the administrative certification etc) so those services do need help from the community to mature.

For deployment/restart I did think that we need to be able to specify very exact deployment windows (such as deploy latest certificate every Wednesday at 6pm) - so say your actual certificate renewal happened automatically earlier in the week , the actual deployment+restart would either be manual or otherwise controlled to avoid unexpected interruptions for users.

Here is the post renewal script that works for me. It updates the 4 core certificates associated with remote desktop services; specifically remoteapp deployments.

THis is done on 2012r2. The RDS path is only available on newer versions of windows server…

param($result)

Import-Module RemoteDesktopServices
Import-Module RemoteDesktop

#Set the certificate for the 4 core RDS services
Set-RDCertificate -Role RDGateway -ImportPath $result.ManagedItem.CertificatePath -Force
Set-RDCertificate -Role RDWebAccess -ImportPath $result.ManagedItem.CertificatePath -Force
Set-RDCertificate -Role RDRedirector -ImportPath $result.ManagedItem.CertificatePath -Force
Set-RDCertificate -Role RDPublishing -ImportPath $result.ManagedItem.CertificatePath -Force

1 Like

How do you install all certificates if you’re not using a connection broker? Stumped…