Today we're looking at automated management of Let's Encrypt certificates with PowerShell and a CMG.
Let's Encrypt and PowerShell
Awhile back I was playing around with Let's Encrypt in order to get as much of my home lab working properly with HTTPS and minimal client configuration. I found many Linux resources and Docker containers to be straightforward, but Windows proved more difficult to me. Then I discovered Posh-ACME (https://poshac.me/docs/v4/), a PowerShell module used for creating and maintaining Let's Encrypt certificates. Why use PowerShell? Well, I use it regularly so this module easily integrates with the tools I'm used to. I also wanted to deploy my Let's Encrypt certificates using a DNS challenge, as opposed to the standard HTTP challenge that requires port 80 being opened and automate the process. Fortunately, Posh-ACME supports all 3 challenge methods: HTTP, DNS, and TLS.
After playing with the module for a bit, I came across a blog post by Aaron Sadler that detailed what I wanted to do at the time. In his post, he details 2 scripts: one that will generate a Let's Encrypt certificate using a DNS challenge, install Windows Admin Center (https://aka.ms/WindowsAdminCenter), and configure it to use your new Let's Encrypt certificate and another script that can run as a Scheduled Task. This script will check and renew the certificate if needed, while re-configuring Windows Admin Center with the new certificate. This approach is great and has been humming along in my lab for quite some time now. Aaron's post can be found here (thank you, Aaron!): https://aaronsadler.dev/2019/september/20/using-let-s-encrypt-certificates-with-windows-admin-center/
Automating CMG Certificates
These days I want to maintain certain aspects of my lab with little effort and I thought I could use this same method to create and renew certificates for my CMG. Last week, it turned out Michael Niehaus had a similar thought which helped me finally get moving on this post. 🙂 https://oofhours.com/2023/08/23/creating-a-wildcard-cert-on-windows-using-letsencrypt/
Below, you'll notice I reused much of the code in Aaron's scripts but reworked them to use my DNS provider and the Configuration Manager PowerShell module to configure the CMG.
The scripts
The first script to request the initial certificate requires you specify the Let's Encrypt environment you want to generate the certificate from. I recommend starting with LE_STAGING while testing to avoid hitting the rate limits of LE_PROD. Once your script is functional, switch to LE_PROD.
Next, specify credentials for the DNS challenge. I use Namecheap's DNS for my domain, but there are many plugins built-in to the module that allow you to choose between many popular DNS services. That documentation can be found here: https://poshac.me/docs/v4/Plugins/. Then, be sure to adjust the variables at the beginning as well as the parameters used with New-PACertificate.
Additionally, it's important to point out this note from Aaron if the scripts will be used in production: "In a production environment the following steps should be performed as a separate (batch/script) account. Posh-ACME saves the settings in the user profile and you need to schedule a task to update the certificates. You do not want to schedule a task with your regular user."
# Specify the environment to acquire certificates from (LE_PROD is Let's Encrypt production environment and LE_STAGE is the test environment).
Set-PAServer LE_PROD
# Credentials for Namecheap
$ncKey = Read-Host "ENTER NAMECHEAP API KEY" -AsSecureString
# Parameters for Namecheap and CMG
$ncParams = @{NCUsername='NamecheapUsername';NCApiKey=$ncKey}
$certDomain = 'CMG01.corp.viamonstra.com'
$certPass = ConvertTo-SecureString -String "poshacme" -AsPlainText -Force
$CMGname = 'CMG01'
# Acquire the certificate:
$newCert = New-PACertificate $CertDomain -AcceptTOS -Install -Contact mail@corp.viamonstra.com -DnsPlugin NameCheap -PluginArgs $ncParams -verbose -force
#Configure the CMG with the new certificate
Import-Module "E:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1" -DisableNameChecking | Out-Null
$SiteInfo = Get-CIMInstance -Namespace "root\SMS" -ClassName "SMS_ProviderLocation"
Set-Location "$($SiteInfo.SiteCode):\"
$CMG = Get-CMCloudManagementGateway | Where-Object ServiceCName -eq $certDomain
if ($CMG -ne $null)
{
Set-CMCloudManagementGateway -Name $CMGname -ServiceCertPath $newCert.pfxFile -ServiceCertPassword $certPass
}
The renewal script has much less configuration because it relies on the already existing certificate. Make sure you specify the correct certDomain and CMGname.
# Update existing certificate
# This task should be scheduled to run every day (or something similar)
Import-Module Posh-ACME
# Specify the domainname to update:
$certDomain = 'CMG01.corp.viamonstra.com'
$CMGname = 'CMG01'
$certPass = ConvertTo-SecureString -String "poshacme" -AsPlainText -Force
# Get the current certificate:
$currentCert = Get-PAOrder | Where-Object Name -eq $certDomain
# Specify the environment (Production or Test)
Set-PAServer LE_PROD
# Specify what certificate to renew
Set-PAOrder -MainDomain $CertDomain
# Submit the renewal
$newCert = Submit-Renewal -Force
if ($newCert -ne $null)
{
# If atleast one new certificate is returned:
foreach ($c in $newCert)
{
# Check if the returned certificate matches the domainname specified:
if ($c.AllSANs -contains $CertDomain)
{
Import-Module "E:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1" -DisableNameChecking | Out-Null
$SiteInfo = Get-CIMInstance -Namespace "root\SMS" -ClassName "SMS_ProviderLocation"
Set-Location "$($SiteInfo.SiteCode):\"
$CMG = Get-CMCloudManagementGateway | Where-Object ServiceCName -eq $certDomain
if ($CMG -ne $null)
{
Set-CMCloudManagementGateway -Name $CMGname -ServiceCertPath $newCert.pfxFile -ServiceCertPassword $certPass
}
}
}
}
After running the scripts, I can confirm my CMG is now using the Let's Encrypt certificate:


Note: this version of the scripts assume you already have a CMG configured. I plan to write a follow up post on the end-to-end creation of a CMG with Let's Encrypt certificates.
If you see ways of improving these scripts, please drop them into the comments!
Thanks,
Andrew