Exchange ACME PowerShell Script

This is the PowerShell script I use to automatically update a Let’s Encrypt SSL Certificate on Exchange 2016 running on Windows Server 2016.  Let’s Encrypt certificates are valid for 3 months, but I set the script to run once a month, so that if there is some type of temporary problem it gets two more tries before the expiration.

# Install and enable the necessary modules.
Install-Module ACMESharp
Install-Module ACMESharp.Providers.IIS
Import-Module ACMESharp
if (-Not (Test-Path C:\ProgramData\ACMESharp\sys-exts\ACMESharp.Providers.IIS.extlnk)) {Enable-ACMEExtensionModule ACMESharp.Providers.IIS}

# Configure the globle variables.
$registration_email = "email@domain.com"
$CN = "mail.domain1.com"
$SAN1 = "autodiscover.domain1.com"
$SAN2 = "mail.domain2.com"
$SAN3 = "autodiscover.domain1.com"
$CN_identifier = "$($CN)_$($(get-date -format yyyy-MM-dd--HH-mm))"
$SAN1_identifier = "$($SAN1)_$($(get-date -format yyyy-MM-dd--HH-mm))"
$SAN2_identifier = "$($SAN2)_$($(get-date -format yyyy-MM-dd--HH-mm))"
$SAN3_identifier = "$($SAN3)_$($(get-date -format yyyy-MM-dd--HH-mm))"
$acme_vault = Get-ACMEVault
$certificate_path = "C:\ACME_Certificates"
$authorization_path = "C:\inetpub\wwwroot\.well-known"
$acme_log = "$($certificate_path)\acme_$($(get-date -format yyyy-MM-dd--HH-mm)).log"
$certificate_alias = "$($CN)_$($(get-date -format yyyy-MM-dd--HH-mm))"
$certificate_pfxfile = "$($certificate_path)\$($certificate_alias).pfx"

# Create the register domain function.
function Register-Domain
{
    # Get the domain and the domain identifier.
    $domain = $args[0]
    $domain_identifier = $args[1]

    # Create the identifier.
    echo "`n Creating a new identifier $domain_identifier for $domain."
    New-ACMEIdentifier -Dns $domain -Alias $domain_identifier| select status, Expires

    # Complete the challenge.
    echo "`n Completing the challenge for $domain_identifier for $domain."
    Complete-ACMEChallenge $domain_identifier -ChallengeType http-01 -Handler iis -HandlerParameters @{ WebSiteRef = 'Default Web Site' }

    # Submit the challenge.
    echo "`n Submitting $domain_identifier for $domain."
    Submit-ACMEChallenge $domain_identifier -ChallengeType http-01 | select Identifier, status, Expires *>&1 >> $acme_log

    do
    {
        # Check the status of the challenge.
        $authorization_status = (Update-ACMEIdentifier $domain_identifier | FL Status) | Out-String
        
        # Exit if the result is "invlaid"
        if($authorization_status -like "*invalid*") {break}
        
        # Display the current authorization status.
        echo "`n Authorization is $authorization_status"

        # Wait three seconds if the authorization is not valid.
        if (-Not ($authorization_status -like "*valid*")) {Start-Sleep -s 3}
    }until ($authorization_status -like "*valid*")
    
    # Update the identifier.
    Update-ACMEIdentifier $domain_identifier | select Identifier, status, Expires *>&1 >> $acme_log
}

# Prepare the vault if it doesn't exist.
if (-Not $acme_vault)
{
    # Initialize the vault.
    Initialize-ACMEVault

    # Register the email
    New-ACMERegistration -Contacts mailto:$registration_email -AcceptTos
}

# Create the certificate path if it doesn't exist.
if (-Not (Test-Path $certificate_path)) {New-Item -Path $certificate_path -ItemType Directory}

# Create the authorization path if it doesn't exist.
if (-Not (Test-Path $authorization_path))
{
    # Create the authorization path.
    New-Item -Path $authorization_path -ItemType Directory
    
    # Enable the authorization path to respond to HTTP requests.
    C:\windows\system32\inetsrv\appcmd.exe set config "Default Web Site/.well-known" -section:system.webServer/security/access /sslFlags:"None" /commit:apphost
}

# Register the domains.
Register-Domain $CN $CN_identifier
Register-Domain $SAN1 $SAN1_identifier
Register-Domain $SAN2 $SAN2_identifier
Register-Domain $SAN3 $SAN3_identifier
# Generate a new certificate.
New-ACMECertificate -Identifier $CN_identifier -AlternativeIdentifierRefs $SAN1_identifier,$SAN2_identifier,$SAN3_identifier -Alias $certificate_alias -Generate *>&1 >> $acmelog

# Submit the certificate.
Submit-ACMECertificate -Ref $certificate_alias

# Wait until the certificate is issued.
while (-Not (Update-ACMECertificate $certificate_alias | select IssuerSerialNumber))
{
    echo "`n Waiting for the certificare to be issued."
    Start-Sleep 3
}

# Move to the certificate path.
cd $certificate_path

# Export the certificate in PKCS format.
Get-ACMECertificate -Ref $certificate_alias -ExportPkcs12 $certificate_pfxfile -CertificatePassword 'Password'

# Invoke the Exchange Management PowerShell snapin.
Invoke-Expression "Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn;"

# Import the certificate into Exchange and set it to run all the necessary services.
Import-ExchangeCertificate -FileName $certificate_pfxfile -FriendlyName $certificate_alias -Password (ConvertTo-SecureString -String 'Password' -AsPlainText -Force) | Enable-ExchangeCertificate -Services POP,IMAP,SMTP,IIS -Force

# Restart IIS.
iisreset

# Remove the authorization path and the PKCS certificate (optional).
Remove-Item $authorization_path -Force -Recurse
Remove-Item $certificate_pfxfile -Force

# Disable "Require SSL" for the Default Web Site. This allows IIS to redirect HTTP requests to HTTPS if configured (optional).
Set-WebConfiguration -Location "Default Web Site" -Filter 'system.webserver/security/access' -Value Non