Last active
June 6, 2023 09:15
-
-
Save kovachwt/016576aa87383b05ac0ceda18fd2fc4b to your computer and use it in GitHub Desktop.
Let’s Encrypt for Windows and IIS, using the ACME-PS powershell module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import-module 'ACME-PS' | |
# This is an updated Let's Encrypt script using the ACME-PS module https://github.com/PKISharp/ACME-PS | |
# The original script (using ACMESharp) is by Marc Durdin https://marc.durdin.net/2017/02/lets-encrypt-on-windows-redux/ | |
# This directory is used to store your account key and service directory urls as well as orders and related data | |
$acmeStateDir = "C:\Certs\AcmeState"; | |
# | |
# Script parameters | |
# | |
$domain = "example.com" | |
$alsodowww = $true | |
$certname = "$domain-$(get-date -format yyyy-MM-dd--HH-mm)" | |
$iissitename = "Default Web Site" | |
$documentRoot = "C:\inetpub\wwwroot"; | |
# | |
# Environmental variables | |
# | |
$PSEmailServer = "localhost" | |
$LocalEmailAddress = "[email protected]" | |
$OwnerEmailAddress = "[email protected]" | |
$pfxfile = "C:\Certs\$certname.pfx" | |
$CertificatePassword = "PASSWORD!" | |
# | |
# Script setup - should be no need to change things below this point | |
# | |
$ErrorActionPreference = "Stop" | |
$EmailLog = @() | |
# | |
# Utility functions | |
# | |
function Write-Log { | |
Write-Host $args[0] | |
$script:EmailLog += $args[0] | |
} | |
Try { | |
Write-Log "Generating a new identifier for $domain" | |
$dnsIdentifiers = @($domain); | |
if ($alsodowww) { | |
Write-Log "And www.$domain" | |
$dnsIdentifiers = @($domain, "www.$domain"); | |
} | |
Write-Log "Creating a new order for $dnsIdentifiers" | |
$order = New-ACMEOrder -State $acmeStateDir -Identifiers $dnsIdentifiers; | |
# Fetch the authorizations for that order | |
Write-Log "Fetch the authorizations for order $($order.ResourceUrl)" | |
$authorizations = @(Get-ACMEAuthorization -State $acmeStateDir -Order $order); | |
foreach ($authz in $authorizations) { | |
Write-Log "Get the challenge for authorization $($authZ.ResourceUrl)" | |
$challenge = Get-ACMEChallenge -State $acmeStateDir -Authorization $authZ -Type "http-01"; | |
$chFilename = [System.IO.Path]::Combine($documentRoot, $challenge.Data.RelativeUrl.Replace("/", "\").TrimStart('\')); | |
$chDirectory = [System.IO.Path]::GetDirectoryName($chFilename); | |
# Ensure the challenge directory exists | |
if (-not (Test-Path $chDirectory)) { | |
Write-Log "Creating directory $chDirectory" | |
New-Item -Path $chDirectory -ItemType Directory; | |
} | |
#Write-Log "Writing token $($challenge.Data.Content) to file $chFilename" | |
Write-Log "Writing token data to file $chFilename" | |
Set-Content -Path $chFilename -Value $challenge.Data.Content -NoNewline; | |
Write-Log "Completing challenge $($challenge.Url)" | |
$completechallenge = Complete-ACMEChallenge -State $acmeStateDir -Challenge $challenge | |
} | |
# Wait a little bit and update the order, until we see the status 'ready' or 'invalid' | |
while ($order.Status -notin ("ready", "invalid")) { | |
Start-Sleep -Seconds 10; | |
Write-Log "Updating order $($order.ResourceUrl), status is $($order.Status)" | |
$order = Update-ACMEOrder -State $acmeStateDir -PassThru -Order $order | |
} | |
# Should the order get invalid, use Get-ACMEAuthorizationError to list error details. | |
if ($order.Status -ieq ("invalid")) { | |
$ordererror = Get-ACMEAuthorizationError -State $acmeStateDir -Order $order; | |
throw "Order was invalid,certificate cannot be issued: $ordererror"; | |
} | |
# Complete the order - this will issue a certificate singing request | |
Write-Log "Completing order and making CSR" | |
Complete-ACMEOrder -State $acmeStateDir -Order $order -GenerateCertificateKey; | |
# Now we wait until the ACME service provides the certificate url | |
while (-not $order.CertificateUrl) { | |
Start-Sleep -Seconds 15 | |
$order = Update-ACMEOrder -State $acmeStateDir -PassThru -Order $order | |
} | |
Write-Log "Exporting PFX certificate to $pfxfile" | |
Export-ACMECertificate -State $acmeStateDir -Order $order -Path $pfxfile -Password (ConvertTo-SecureString -String $CertificatePassword -AsPlainText -Force); | |
# Import the certificate to the local machine certificate store | |
Write-Log "Import pfx certificate $pfxfile" | |
$certRootStore = "LocalMachine" | |
$certStore = "My" | |
$pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 | |
$pfx.Import($pfxfile, $CertificatePassword, "Exportable,PersistKeySet,MachineKeySet") | |
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store($certStore, $certRootStore) | |
$store.Open('ReadWrite') | |
$store.Add($pfx) | |
$store.Close() | |
$certThumbprint = $pfx.Thumbprint | |
$store.Close() | |
####Set Certificate | |
$obj = get-webconfiguration "//sites/site[@name='$iissitename']" | |
$binding = $obj.bindings.Collection | where { (($_.protocol -eq "HTTPS") -and ($_.bindingInformation -eq ("*:443:$domain"))) } | |
Write-Log "Replace IIS certificate for site $iissitename in binding $($binding.bindingInformation)" | |
$method = $binding.Methods["AddSslCertificate"] | |
$methodInstance = $method.CreateInstance() | |
$methodInstance.Input.SetAttributeValue("certificateHash", $certThumbprint) | |
$methodInstance.Input.SetAttributeValue("certificateStoreName", $certStore) | |
$methodInstance.Execute() | |
if ($alsodowww) { | |
$binding = $obj.bindings.Collection | where { (($_.protocol -eq "HTTPS") -and ($_.bindingInformation -eq ("*:443:www.$domain"))) } | |
Write-Log "Replace IIS certificate for site $iissitename in binding $($binding.bindingInformation)" | |
$method = $binding.Methods["AddSslCertificate"] | |
$methodInstance = $method.CreateInstance() | |
$methodInstance.Input.SetAttributeValue("certificateHash", $certThumbprint) | |
$methodInstance.Input.SetAttributeValue("certificateStoreName", $certStore) | |
$methodInstance.Execute() | |
} | |
# Finished | |
Write-Log "Finished, see if it works here: https://$domain/" | |
if ($alsodowww) { | |
Write-Log "And here: https://www.$domain/" | |
} | |
$Body = $EmailLog | out-string | |
Send-MailMessage -SmtpServer $PSEmailServer -From $LocalEmailAddress -To $OwnerEmailAddress -Subject "Let's Encrypt certificate renewed for $domain" -Body $Body | |
} | |
Catch { | |
Write-Host $_.Exception | |
$ErrorMessage = $_.Exception | format-list -force | out-string | |
$EmailLog += "Let's Encrypt certificate renewal for $domain failed with exception`n$ErrorMessage`r`n`r`n" | |
$Body = $EmailLog | Out-String | |
Send-MailMessage -SmtpServer $PSEmailServer -From $LocalEmailAddress -To $OwnerEmailAddress -Subject "Let's Encrypt certificate renewal for $domain failed with exception" -Body $Body | |
Exit | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment