Binding Certificate to RDP Service using Post-Push Script
Description
In some scenarios, even after a successful certificate push, the new certificate may not be automatically bound to the RDP service (port 3389), resulting in the old certificate still being used.Solution
A PowerShell script can be used as a post-push script to bind the newly pushed certificate to the RDP service.Ensure that:
- The script should be placed on the target server where the certificate is pushed.
- Configure the script location in the Post Push Script Section in the connector.
Script
try {
# ---- Decode JSON input ----
$request = [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($args[0]))
Write-Host "Checkpoint 1: Base64 decoded successfully."
# Replace unsupported keys for parsing
$request = $request.Replace('dNSNames','dNSNamesIgnore').Replace('iPAddresses','iPAddressesIgnore')
# Convert JSON to PowerShell object
$json_request = ConvertFrom-Json -InputObject $request
Write-Host "Checkpoint 2: JSON parsed successfully."
# ---- Extract certificate thumbprint ----
if (-not $json_request.certificate.extension.thumbPrint) {
throw "Thumbprint field not found in JSON input."
}
$vmThumbPrint = $json_request.certificate.extension.thumbPrint.Replace(":", "")
$certThumbprint = $vmThumbPrint.ToUpper().Replace(" ", "").Trim()
Write-Host "Thumbprint Extracted: $certThumbprint"
}
catch {
Write-Host "ERROR parsing JSON / Extracting thumbprint: $($_.Exception.Message)"
Write-Host "AppResponseCode:1"
exit 1
}
Write-Host "Searching for certificate with thumbprint $certThumbprint ..."
# Step 1: Locate certificate
$storesToCheck = @("Remote Desktop", "My")
$cert = $null
foreach ($storeName in $storesToCheck) {
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store($storeName, "LocalMachine")
$store.Open("ReadOnly")
$found = $store.Certificates | Where-Object { $_.Thumbprint -replace " " -eq $certThumbprint }
if ($found) {
$cert = $found
Write-Host "Certificate found in LocalMachine\$storeName"
$store.Close()
break
}
$store.Close()
}
if (-not $cert) {
Write-Host "ERROR: Certificate not found in My or Remote Desktop store."
Write-Host "AppResponseCode:1"
exit 1
}
Write-Host "Checkpoint: Certificate loaded."
# Locate private key in both CSP & CNG
try {
Add-Type -AssemblyName System.Security
$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$uniqueName = $rsaKey.Key.UniqueName
$cngPath = "C:\ProgramData\Microsoft\Crypto\Keys\$uniqueName"
$cspPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\$uniqueName"
if (Test-Path $cngPath) {
$keyPath = $cngPath
Write-Host "Private key found in CNG Keys folder."
}
elseif (Test-Path $cspPath) {
$keyPath = $cspPath
Write-Host "Private key found in CSP MachineKeys folder."
}
else {
throw "Private key not found in CNG or CSP paths."
}
# Apply NETWORK SERVICE permissions
$acl = Get-Acl $keyPath
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"NETWORK SERVICE","FullControl","Allow"
)
$acl.AddAccessRule($rule)
Set-Acl -Path $keyPath -AclObject $acl
Write-Host "Checkpoint: Key permissions applied successfully."
}
catch {
Write-Host "ERROR setting key permissions: $($_.Exception.Message)"
Write-Host "AppResponseCode:1"
exit 1
}
# Step 3: Write binary thumbprint to RDP registry
try {
$hex = $certThumbprint -replace ' ', ''
$bin = for ($i=0; $i -lt $hex.Length; $i+=2) {
[Convert]::ToByte($hex.Substring($i,2),16)
}
$rdpRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
Set-ItemProperty -Path $rdpRegPath -Name "SSLCertificateSHA1Hash" -Value $bin -Type Binary
Write-Host "Checkpoint: RDP Registry binding updated."
}
catch {
Write-Host "ERROR updating RDP registry: $($_.Exception.Message)"
Write-Host "AppResponseCode:1"
exit 1
}
# Step 4: Restart TermService
try {
Restart-Service TermService -Force
Write-Host "TermService restarted successfully."
Write-Host "Certificate bound to RDP successfully."
Write-Host "AppResponseCode:0"
}
catch {
Write-Host "ERROR restarting TermService: $($_.Exception.Message)"
Write-Host "AppResponseCode:1"
exit 1
}
