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 = “[email protected]” # Your email or sender email approved in SMTP2Go
$smtpTo = “[email protected]” # 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 = "[email protected]" # Your email or sender email approved in SMTP2Go
$smtpTo = "[email protected]" # 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 = "[email protected]"
$smtpTo = "[email protected]"
$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.