Automating certificate renewal via Powershell for RD Connection Broker SSO, RD Conenction Broker Publishing, RD Web Access, and RD Gateway

Hello all, first time post and first time trying to use CertifyTheWeb. Very excited to get started! I’ve created a script to automate renewing the certificate for all RDS roles. The script works well when I run it natively in Powershell, but CertifyTheWeb console throws the following error:

The deployment task failed to complete. Run…: Run…: Running script [C:\SSL_Update_Test.ps1]
Error Running Script: System.ComponentModel.Win32Exception (0x80004005): The specified executable is not a valid application for this OS platform.
at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
at Certify.Providers.DeploymentTasks.Script.RunLocalScript(ILog log, String command, String args, DeploymentTaskConfig settings, Dictionary2 credentials, Int32 timeoutMins, Boolean launchNewProcess) Error: System.InvalidOperationException: No process is associated with this object. at System.Diagnostics.Process.EnsureState(State state) at System.Diagnostics.Process.get_HasExited() at Certify.Providers.DeploymentTasks.Script.RunLocalScript(ILog log, String command, String args, DeploymentTaskConfig settings, Dictionary2 credentials, Int32 timeoutMins, Boolean launchNewProcess)

Here is my script:

Parameters

$domainName = “redacted.redacted.com
$rdServices = @(“RDRedirector”, “RDPublishing”, “RDWebAccess”, “RDGateway”)
$connectionBrokerName = “REDACTED.redacted.com
$log = @() # Initialize log array

Email parameters

$smtpServer = “mail.smtp2go.com” # SMTP2Go SMTP server address
$smtpPort = 587 # Or 2525, 8025, 25, 465 (SSL/TLS) based on your SMTP2Go settings
$smtpFrom = “redacted@redacted.com” # Your email or sender email approved in SMTP2Go
$smtpTo = “redacted@redacted.com” # Recipient email address
$smtpUser = “sslreport” # SMTP2Go username
$smtpPass = “REDACTED” # SMTP2Go password

$smtpPassSecure = ConvertTo-SecureString $smtpPass -AsPlainText -Force
$smtpCredential = New-Object System.Management.Automation.PSCredential ($smtpUser, $smtpPassSecure)

Function to apply certificate to RD Role and log the operation

function Set-RDCertificate {
param (
[Parameter(Mandatory=$true)]
[string]$role,

    [Parameter(Mandatory=$true)]
    [string]$thumbprint
)

try {
    Import-Module RemoteDesktop

    # Correct the cmdlet call to use the actual PowerShell cmdlet and parameters
    $roleCert = Get-Item -Path Cert:\LocalMachine\My\$thumbprint
    Invoke-Command -ScriptBlock { Set-RDCertificate -Role $using:role -ImportPath $using:roleCert.PSPath -Force -ConnectionBroker $using:connectionBrokerName }
    $global:log += "Successfully applied certificate to $role"
}
catch {
    $global:log += "Error applying certificate to $role"
    throw
}

}

Main script

try {
# Import the RemoteDesktop Module
Import-Module RemoteDesktop

Define the issuer you’re interested in

$issuer = “CN=R3, O=Let’s Encrypt, C=US”

Get the latest certificate with the specified issuer for the domain

$cert = Get-ChildItem -Path Cert:\LocalMachine\My\ |
Where-Object { $.Subject -like “CN=$domainName” -and $.Issuer -eq $issuer } |
Sort-Object -Property NotBefore -Descending |
Select-Object -First 1

$thumbprint = $cert.Thumbprint
$log += "Found certificate with thumbprint: $thumbprint"

Apply the certificate to each RD role without confirmation prompts

foreach ($role in $rdServices) {
    Set-RDCertificate -role $role -thumbprint $thumbprint -ConnectionBroker $connectionBrokerName -Force
}

# Restart services to apply the new certificates
try {
    Restart-Service -Name "Tssdis" -Force
    $log += "Successfully restarted the RD Connection Broker service."
}
catch {
    $log += "Failed to restart the RD Connection Broker service"
}

try {
    Restart-Service -Name "W3SVC" -Force
    $log += "Successfully restarted IIS (W3SVC) for RD Web Access."
}
catch {
    $log += "Failed to restart IIS (W3SVC) for RD Web Access"
}

try {
    Restart-Service -Name "TSGateway" -Force
    $log += "Successfully restarted the RD Gateway service."
}
catch {
    $log += "Failed to restart the RD Gateway service"
}

# Prepare success message with log details
$successMessage = "SSL certificates have been applied to all RD roles successfully.`n`nDetails:`n" + ($log -join "`n")
Send-MailMessage -From $smtpFrom -To $smtpTo -Subject "RD SSL Certificate Update Success" -Body $successMessage -SmtpServer $smtpServer -Credential $smtpCredential -UseSsl

}
catch {
# Prepare error message with log details
$errorMessage = “An error occurred during RD SSL Certificate Update.nnError Details:n$_nnLog Details:n” + ($log -join “`n”)
Send-MailMessage -From $smtpFrom -To $smtpTo -Subject “RD SSL Certificate Update Failure” -Body $errorMessage -SmtpServer $smtpServer -Credential $smtpCredential -UseSsl
}

I’m guessing I’m missing something relatively simple. Any ideas?

Thanks,

Mike

Hi @neatpinkshark - the app has a bunch of built in deployment tasks to do various things and one of them is Run a PowerShell Script, you should use this for most custom scripting as it can be passed the details of the renewal object, see the scripting docs for more info: Scripting | Certify The Web Docs

Your error message about shows you were trying to use the Run a Script task which on windows runs a .bat file and on linux would run a shell script etc.

If using powershell you should not try to determine the certificate details (thumbprint etc) yourself because this information is already provided in the $result param. As certs can have multiple domains includes matching on subject is a little non-specific.

Before writing your own script check if our basic deployment tasks will work for you, some of which use these scripts: certify-plugins/src/DeploymentTasks/Core/Providers/Assets at development · webprofusion/certify-plugins · GitHub

A renewal can have multiple deployment tasks and you can set whether they care if the last step failed etc.

Hi @webprofusion, thank you for your quick response! I somehow managed to entirely miss the “Run a PowerShell Script” task…in my defense, it was a long week and I already had one foot in the weekend :slight_smile:

I’ve reviewed the documentation you suggested, and while I may be missing something it appears to be setting the certificate for the Gateway, but I don’t see where it sets the certificate for the other roles, “RDRedirector”, “RDPublishing”, and “RDWebAccess”.

My script seems to be doing all of what I need it to do, and (thanks to you pointing out the “Run a PowerShell Script” task) it is running via Certify the Web console job, but I’m having issues modifying it to test/use the $results param as you mentioned above. I created a script to output the properties and variables of the $results param and added this as a job in the Certify The Web console, but the output appears to be null:

Starting the check for $result at 02/26/2024 10:02:03
The result variable ($result) is null or not provided.
Final value of $result: 

Below is the modified script, attempting to use the $results param. The resulting email simply states:

An error occurred during RD SSL Certificate Update.

Error Details:
Certificate result (\) is null or not provided.

Any suggestions?

## Parameters
$domainName = "redacted.redacted.com"
$rdServices = @("RDRedirector", "RDPublishing", "RDWebAccess", "RDGateway")
$connectionBrokerName = "REDACTED.redacted.com"
$log = @() # Initialize log array

# Email parameters
$smtpServer = "mail.smtp2go.com" # SMTP2Go SMTP server address
$smtpPort = 587 # Or 2525, 8025, 25, 465 (SSL/TLS) based on your SMTP2Go settings
$smtpFrom = "redacted@redacted.com" # Your email or sender email approved in SMTP2Go
$smtpTo = "redacted@redacted.com" # Recipient email address
$smtpUser = "redacted" # SMTP2Go username
$smtpPass = "redacted" # SMTP2Go password

$smtpPassSecure = ConvertTo-SecureString $smtpPass -AsPlainText -Force
$smtpCredential = New-Object System.Management.Automation.PSCredential ($smtpUser, $smtpPassSecure)

# Function to apply certificate to RD Role and log the operation
function Set-RDCertificate {
    param (
        [Parameter(Mandatory=$true)]
        [string]$role,
        
        [Parameter(Mandatory=$true)]
        [string]$thumbprint
    )

    try {
        Import-Module RemoteDesktop

        # Correct the cmdlet call to use the actual PowerShell cmdlet and parameters
        $roleCert = Get-Item -Path Cert:\LocalMachine\My\$thumbprint
        Invoke-Command -ScriptBlock { Set-RDCertificate -Role $using:role -ImportPath $using:roleCert.PSPath -Force -ConnectionBroker $using:connectionBrokerName }
        $global:log += "Successfully applied certificate to $role"
    }
    catch {
        $global:log += "Error applying certificate to $role"
        throw
    }
}

# Main script
try {
    # Import the RemoteDesktop Module
    Import-Module RemoteDesktop

    # Assume that $result contains the certificate object with a thumbprint property
    if ($null -eq $result) {
        throw "Certificate result (\$result) is null or not provided."
    }
    
    if ($null -eq $result.Thumbprint) {
        throw "Certificate thumbprint is null or not provided in the result."
    }

    $thumbprint = $result.Thumbprint
    $log += "Using certificate with thumbprint: $thumbprint"

    # Apply the certificate to each RD role without confirmation prompts
    foreach ($role in $rdServices) {
        Set-RDCertificate -role $role -thumbprint $thumbprint
    }

     # Restart services to apply the new certificates
    try {
        Restart-Service -Name "Tssdis" -Force
        $log += "Successfully restarted the RD Connection Broker service."
    }
    catch {
        $log += "Failed to restart the RD Connection Broker service"
    }
    
    try {
        Restart-Service -Name "W3SVC" -Force
        $log += "Successfully restarted IIS (W3SVC) for RD Web Access."
    }
    catch {
        $log += "Failed to restart IIS (W3SVC) for RD Web Access"
    }

    try {
        Restart-Service -Name "TSGateway" -Force
        $log += "Successfully restarted the RD Gateway service."
    }
    catch {
        $log += "Failed to restart the RD Gateway service"
    }

    # Prepare success message with log details
    $successMessage = "SSL certificates have been applied to all RD roles successfully.`n`nDetails:`n" + ($log -join "`n")
    Send-MailMessage -From $smtpFrom -To $smtpTo -Subject "RD SSL Certificate Update Success" -Body $successMessage -SmtpServer $smtpServer -Credential $smtpCredential -UseSsl
}
catch {
    # Prepare error message with log details
    $errorMessage = "An error occurred during RD SSL Certificate Update.`n`nError Details:`n$_`n`nLog Details:`n" + ($log -join "`n")
    Send-MailMessage -From $smtpFrom -To $smtpTo -Subject "RD SSL Certificate Update Failure" -Body $errorMessage -SmtpServer $smtpServer -Credential $smtpCredential -UseSsl
}

Thanks, you just use 3 backticks to start a code block on your forum post then close the block with another 3 backticks (edited it for you).

Does your script start with param($result) - that’s how powershell scripts accept input parameters and that would be how $result should get populated. See more examples at Scripting | Certify The Web Docs

The property for the thumbprint is $result.ManagedItem.CertificateThumbprintHash not $result.Thumbprint as the toplevel result is the status object of the renewal. Other than that it looks ok to me, I think.

Thank you @webprofusion, that ended up working!

Here is the corrected code that did the trick:

param (
    [Parameter(Mandatory=$false)]
    [object]$result
)

# Set preferences for verbose output
$VerbosePreference = 'SilentlyContinue'
$ConfirmPreference = 'None'

# Initialize logging mechanism
$logPath = "C:\LogFile.log"
function Log-Message {
    param (
        [Parameter(Mandatory=$true)]
        [string]$Message
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "$timestamp - $Message"
    Add-Content -Path $logPath -Value $logMessage
}

# Import required modules
Import-Module RemoteDesktop

# Script parameters
$domainName = "redacted.redacted.com"
$rdServices = @("RDRedirector", "RDPublishing", "RDWebAccess", "RDGateway")
$connectionBrokerName = "REDACTED.redacted.com"

# Email parameters
$smtpServer = "mail.smtp2go.com"
$smtpPort = 587
$smtpFrom = "redacted@redacted.com"
$smtpTo = "redacted@redacted.com"
$smtpUser = "redacted"
$smtpPass = "REDACTED"
$smtpPassSecure = ConvertTo-SecureString $smtpPass -AsPlainText -Force
$smtpCredential = New-Object System.Management.Automation.PSCredential ($smtpUser, $smtpPassSecure)

# Function to apply certificate to RD Role and log the operation
function Set-RDCertificate {
    param (
        [Parameter(Mandatory=$true)]
        [string]$role,
        [Parameter(Mandatory=$true)]
        [string]$thumbprint,
        [Parameter(Mandatory=$true)]
        [string]$connectionBrokerName
    )
    
    try {
        RemoteDesktop\Set-RDCertificate -Role $role -Thumbprint $thumbprint -ConnectionBroker $connectionBrokerName -Force -Verbose
        Log-Message "Successfully applied certificate to $role"
    }
    catch {
        $errorMessage = "Error applying certificate to $role. Error: $_"
        Log-Message $errorMessage
        Send-ErrorReport -ErrorMessage $errorMessage
        exit
    }
}

# Function to send error reports
function Send-ErrorReport {
    param (
        [Parameter(Mandatory=$true)]
        [string]$ErrorMessage
    )
    
    $body = "An error occurred: $ErrorMessage"
    try {
        Send-MailMessage -From $smtpFrom -To $smtpTo -Subject "RD SSL Certificate Update Error" -Body $body -SmtpServer $smtpServer -Port $smtpPort -Credential $smtpCredential -UseSsl
    }
    catch {
        $errorSendingMsg = "Failed to send error report. Error: $_"
        Log-Message $errorSendingMsg
    }
}

# Main script execution
try {
    Log-Message "Starting script to update RD SSL Certificate."
    
    # Log properties of the $result object
    Log-Message "Result object properties:"
    Log-Message "IsSuccess: $($result.IsSuccess)"
    Log-Message "Message: $($result.Message)"
    Log-Message "ManagedItem: $($result.ManagedItem)"
    
    # Attempt to find the certificate
    $thumbprint = $result.ManagedItem.CertificateThumbprintHash
    Log-Message "Found certificate with thumbprint: $thumbprint"

    foreach ($role in $rdServices) {
        Set-RDCertificate -role $role -thumbprint $thumbprint -ConnectionBroker $connectionBrokerName
    }

    Log-Message "Restarting required services."
    Restart-Service -Name "Tssdis", "W3SVC", "TSGateway" -Force
    Log-Message "Services restarted successfully."

    # Send success email
    $successMessage = "SSL certificates have been applied to all RD roles successfully."
    Send-MailMessage -From $smtpFrom -To $smtpTo -Subject "RD SSL Certificate Update Success" -Body $successMessage -SmtpServer $smtpServer -Port $smtpPort -Credential $smtpCredential -UseSsl
}
catch {
    Log-Message "An unexpected error occurred: $_"
    Send-ErrorReport -ErrorMessage $_
}

Log-Message "Script execution completed."

Thank you so much for all your assistance, it is greatly appreciated!

1 Like

Great, glad you got it working! We should probably build in something similar for other users as our current remote desktop related tasks are a bit fragmented and not really doing enough out of the box.

Im in the same boat: latest version of CTW doesn’t add the cert to the 4 rds roles, even tho im using the “deploy to RDP service gateway” and “deploy to RDP listener service” deployment tasks and they run successfully. So I am trying your script. I edited it to my needs and ran it in powershell to see if it works but the log file says “An unexpected error occurred. Cannot bind argument to parameter “thumbprint” because it is an empty string.” What does this mean and whats the fix?

Sorry can’t tell without seeing your full script, I assume you have already acquired a cert and are testing your script against that, as there would be no thumbprint if you don’t have a cert yet but it could also be a mistake in variable assignment etc. You can paste scripts here by enclosing them in three backticks `at start and end of the code block.

Thanks for the reply. My script is exactly as the one @neatpinkshark posted on Feb 28, except I have my details filled in where he has written “redacted” and Im using my SMTP relay.

Yes, I have already generated a LE cert using CTW and Im running this powershell script in powershell to test its working condition, before adding it as a deployment task.

Thanks, see Scripting | Certify The Web Docs

All powershells scripts will expect a $result parameter which includes essential information about the certificate and it’s renewal status, that includes the thumbprint info.

If you just try running the script outside of Certify you’re probably not passing it the result argument properly because that’s a nested object that would be tricky to create manually.

I can correlate with what @webprofusion said; the script as written has to be executed as a task within the CertifyTheWeb console in order to work properly, it won’t be able to run properly when executed externally as a native powershell script.

1 Like

makes sense! thanks guys. ill give it a whirl.

1 Like

So I ran the script in my deployment task and most of it is successful.

These are my deployment tasks:

This is a link to my ps1 script and the log file:
My Script and Log

The script applies to the ‘Rd Connection Broker’ Role but does not apply to the ‘RD Web Access’ role nor the ‘RD Gateway’ role. The error in the log states: “An error occurred: Error applying certificate to RDWebAccess. Error: Object reference not set to an instance of an object.” My RD server, where CTW is installed, is windows server 2019 datacenter.

Can you help?

@KTC_Cory Try echoing out the thumbprint you need to run the command manually (or it’s also found under Certficate > Advanced > Actions) then try running the single Set-RDCertificate command from powershell.

Your RDGateway step doesn’t run because your scripts exits on error (and RDWebAccess errors first).

Try echoing out the thumbprint you need to run the command manually (or it’s also found under Certficate > Advanced > Actions)

Im not sure I understand what this means. Can you clarify? And thanks SO much for the help.

By ‘echoing’ I mean printing out or writing out the value to the log

e.g. echo $thumbprint

My mind actually made up the UI option to get the thumbprint it doesn’t exist at all currently! You can get it from windows but just using echo in your script it easier.