It let's you create signed certificates for your website for free.
So a safe connection to your website is no longer gonna cost you money, so there is no more reason not to offer it to your visitors.
This script by Franky's web (a German Exchange enthusiast) put this script up on his site to create automatically create a certificate request and have it approved if valid and set as the default certificate in IIS for Exchange 2010, 2013 and 2016.
Now the script on his site is in German, and since it requests some input I translated it to English with the idea that as many people as possible can use it.
Download the script here or copy and paste it from below.
All thanks and credit go out to Frank Zochling and Bjoern over at https://www.frankysweb.de
Param( [bool]$Renew ) clear write-host "" write-host "----------------------------------------------------------------" write-host " _______ _ ___ _ (_______) _ (_) / __|_) _ _ _____ ____ _| |_ _ _| |__ _ ____ _____ _| |_ _____ | | | ___ |/ ___|_ _) (_ __) |/ ___|____ (_ _) ___ | | |_____| ____| | | |_| | | | | ( (___/ ___ | | |_| ____| \______)_____)_| \__)_| |_| |_|\____)_____| \__)_____) _______ _ (_______) (_) _ _ _______ ___ ___ _ ___ _| |_ _____ ____ _| |_ | ___ |/___)/___) |/___|_ _|____ | _ (_ _) | | | |___ |___ | |___ | | |_/ ___ | | | || |_ |_| |_(___/(___/|_(___/ \__)_____|_| |_| \__) " -foregroundcolor cyan write-host "" write-host " Certificate Assistant v1.1" write-host " Automatic Let's Encrypt Certificate for Exchange 2010/2013/2016" write-host "" write-host " Frank Zoechling (www.FrankysWeb.de)" write-host "" write-host "----------------------------------------------------------------" #ACME Sharp Modul laden write-host " Loading ACMESharp Module..." Import-Module ACMESharp -ea 0 $CheckACMEModule = get-module ACMESharp if (!$CheckACMEModule) { write-host " Warning: ACME Sharp Module not found" -foregroundcolor yellow write-host " Please install ACMESharp Module" -foregroundcolor yellow Install-Module -Name ACMESharp -RequiredVersion 0.8.1 -AllowClobber #Specific Module version will be installed Import-Module ACMESharp -ea 0 $CheckACMEModule = get-module ACMESharp if (!$CheckACMEModule) { write-host " Failed: ACME Sharp Module couldn't be installed" -foregroundcolor red exit } } #IIS PowerShell Modul laden write-host " Load IIS Webadministration Module" Import-Module Webadministration -ea 0 $CheckIISModule = get-module Webadministration if (!$CheckIISModule) { write-host " Webadministration Module not found" -foregroundcolor red exit } #IIS SnapIn laden write-host " Load Exchange Management Shell..." Add-PSSnapin *exchange* -ea 0 $CheckExchangeSnapin = Get-PSSnapin *exchange* if (!$CheckExchangeSnapin) { write-host " Exchange SnapIn not found" -foregroundcolor red exit } #Exchange-Version erkennen $ExchangeVersion = (Get-ExchangeServer -Identity $env:COMPUTERNAME | ForEach {$_.AdminDisplayVersion}) if ($ExchangeVersion -match "Version 15") { write-host " Exchange Server 2013/2016 recognized" } elseif ($ExchangeVersion -match "Version 14") { write-host " Exchange Server 2010 recognized" } else { write-host " No supported Exchange Server Version was found. Script halted." -foregroundcolor Red exit } if ($ExchangeVersion -match "Version 14" -Or $ExchangeVersion -match "Version 15") { if ($renew -ne $True) { #Search for configured DNS-Names write-host "" write-host " Read Exchange configuration..." $ExchangeServer = (Get-ExchangeServer $env:computername).Name [array]$CertNames += ((Get-ClientAccessServer -Identity $ExchangeServer).AutoDiscoverServiceInternalUri.Host).ToLower() [array]$CertNames += ((Get-OutlookAnywhere -Server $ExchangeServer).ExternalHostname.Hostnamestring).ToLower() [array]$CertNames += ((Get-OabVirtualDirectory -Server $ExchangeServer).Internalurl.Host).ToLower() [array]$CertNames += ((Get-OabVirtualDirectory -Server $ExchangeServer).ExternalUrl.Host).ToLower() [array]$CertNames += ((Get-ActiveSyncVirtualDirectory -Server $ExchangeServer).Internalurl.Host).ToLower() [array]$CertNames += ((Get-ActiveSyncVirtualDirectory -Server $ExchangeServer).ExternalUrl.Host).ToLower() [array]$CertNames += ((Get-WebServicesVirtualDirectory -Server $ExchangeServer).Internalurl.Host).ToLower() [array]$CertNames += ((Get-WebServicesVirtualDirectory -Server $ExchangeServer).ExternalUrl.Host).ToLower() [array]$CertNames += ((Get-EcpVirtualDirectory -Server $ExchangeServer).Internalurl.Host).ToLower() [array]$CertNames += ((Get-EcpVirtualDirectory -Server $ExchangeServer).ExternalUrl.Host).ToLower() [array]$CertNames += ((Get-OwaVirtualDirectory -Server $ExchangeServer).Internalurl.Host).ToLower() [array]$CertNames += ((Get-OwaVirtualDirectory -Server $ExchangeServer).ExternalUrl.Host).ToLower() if ($ExchangeVersion -match "Version 15") { [array]$CertNames += ((Get-OutlookAnywhere -Server $ExchangeServer).Internalhostname.Hostnamestring).ToLower() [array]$CertNames += ((Get-MapiVirtualDirectory -Server $ExchangeServer).Internalurl.Host).ToLower() [array]$CertNames += ((Get-MapiVirtualDirectory -Server $ExchangeServer).ExternalUrl.Host).ToLower() } $CertNames = $CertNames | select –Unique write-host "----------------------------------------------------------------" write-host "" write-host " The following DNS-Namen were found:" write-host "" foreach ($Certname in $CertNames) { write-host " $certname" -foregroundcolor cyan } write-host "" #Add additional names? $AddName = "y" while ($AddName -match "y") { $AddName = read-host " Add additional DNS names to the certificate? (y/n)" if ($AddName -match "y") { $AddHost = read-host " Enter DNS-Names" $CertNames += "$addhost" } } #Output the DNS names write-host "" write-host " The following DNS Names were configured:" write-host "" foreach ($Certname in $CertNames) { write-host " $certname" -foregroundcolor cyan } write-host "" #Email-Address for the ACME Registration write-host " Which E-Mail Address should be used to register with Let's Encrypt?" write-host "" write-host " If a Let's Encrypt registry has already been performed on this computer" write-host " no e-mail address is required." write-host $contact = read-host " E-Mail Address" $contactmail = "mailto:$contact" write-host #Add task to renew? write-host " Do you want to add a scheduled task to automatically renew the" write-host " certificate?" write-host "" $AutoRenewTask = read-host " Automatic renew? (y/n)" if ($AutoRenewTask -match "y") { $username = read-host " Username for the task (Domain\Username)" $SecurePassword = read-host " Password" -AsSecureString } write-host "" #---------------------------------------------- #Create automatic renew task if ($AutoRenewTask -match "y") { $installpath = (get-location).Path #Create a scheduled task $zeitpunkt = "23:00" $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword) $Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) $startTime = "$zeitpunkt" | get-date -format s $taskService = New-Object -ComObject Schedule.Service $taskService.Connect() $rootFolder = $taskService.GetFolder($NULL) $taskDefinition = $taskService.NewTask(0) $registrationInformation = $taskDefinition.RegistrationInfo $registrationInformation = $taskDefinition.RegistrationInfo $registrationInformation.Description = "Let's Encrypt certificaterenewal - www.FrankysWeb.de" $registrationInformation.Author = $username $taskPrincipal = $taskDefinition.Principal $taskPrincipal.LogonType = 1 $taskPrincipal.UserID = $username $taskPrincipal.RunLevel = 0 $taskSettings = $taskDefinition.Settings $taskSettings.StartWhenAvailable = $true $taskSettings.RunOnlyIfNetworkAvailable = $true $taskSettings.Priority = 7 $taskTriggers = $taskDefinition.Triggers $executionTrigger = $taskTriggers.Create(2) $executionTrigger.StartBoundary = $startTime $taskAction = $taskDefinition.Actions.Create(0) $taskAction.Path = "powershell.exe" $taskAction.Arguments = "-Command `"&'$installpath\CertificateAssistant.ps1' -renew:`$true`"" $job = $rootFolder.RegisterTaskDefinition("Let's Encrypt certificaterenewal (www.FrankysWeb.de)" , $taskDefinition, 6, $username, $password, 1) } #---------------------------------------------- clear write-host "" write-host "---------------------------------------------------------------------------" -foregroundcolor green write-host "All information is available, configure the certificate?" -foregroundcolor green write-host "---------------------------------------------------------------------------" -foregroundcolor green write-host "" read-host "Start Configuration? (Enter for continue / STRG+C to abort" write-host "" #Check if a vault already exists write-host "Check if a vault already exists..." $Vault = Get-ACMEVault if (!$Vault) { write-host "No vault found, trying to create new vault..." $CreateVault = Initialize-ACMEVault sleep 1 $Vault = Get-ACMEVault if (!$Vault) { write-host "Error: Vault could not be created" -foregroundcolor red exit } } #Check if Let's Encrypt registry is present write-host "Check Let's Encrypt Registration..." $Registration = Get-ACMERegistration if (!$Registration) { write-host "Warning: No registration was found at Let's Encrypt, new registration is being performed" -foregroundcolor yellow $Registration = New-ACMERegistration -Contacts $contactmail -AcceptTos if (!$Registration) { write-host "Error: Could not register with Let's Encrypt" -foregroundcolor red exit } else { write-host "Registration at Let's Encrypt was done" -foregroundcolor green } } #Prepare Domain Names Validation $CertSubject = ((Get-OutlookAnywhere -Server $ExchangeServer).ExternalHostname.Hostnamestring).ToLower() $ExchangeSANID = 1 foreach ($ExchangeSAN in $CertNames) { $CurrentDate = get-date -format ddMMyyyyhhmm #CurrentDate $ACMEAlias = "Cert" + "$CurrentDate" + "-" + "$ExchangeSANID" $ExchangeSANID++ write-host "New identifier:" write-host " DNS: $ExchangeSAN" write-host " Alias: $ACMEAlias" $NewID = New-ACMEIdentifier -Dns $ExchangeSAN -Alias $ACMEAlias write-host "Prepare validation:" write-host " Alias $ACMEAlias" $ValidateReq = Complete-ACMEChallenge $ACMEAlias -ChallengeType http-01 -Handler iis -HandlerParameters @{ WebSiteRef = 'Default Web Site' } [Array]$ACMEAliasArray += $ACMEAlias if ($ExchangeSAN -eq $CertSubject) {$SubjectAlias = $ACMEAlias} } #Let's Encrypt IIS directory to HTTP write-host "Let's Encrypt IIS Change the directory to HTTP..." $IISDir = Set-WebConfigurationProperty -Location "Default Web Site/.well-known" -Filter 'system.webserver/security/access' -name "sslFlags" -Value None $IISDirCeck = (Get-WebConfigurationProperty -Location "Default Web Site/.well-known" -Filter 'system.webserver/security/access' -name "sslFlags").Value if ($IISDirCeck -match 0) { write-host "Change to HTTP successfully" -foregroundcolor green } else { write-host "Error: Change to HTTP was unsuccessful" -foregroundcolor red exit } #Validate Domain Names write-host "Let DNS names be validated by Let's Encrypt..." foreach ($ACMEAlias in $ACMEAliasArray) { write-host "Carry out validation: $ACMEAlias" $Validate = Submit-ACMEChallenge $ACMEAlias -ChallengeType http-01 } write-host "wait 30 Seconds..." sleep -seconds 30 #Check the validation write-host "Check whether the DNS names have been validated..." foreach ($ACMEAlias in $ACMEAliasArray) { write-host "Update Alias: $ACMEAlias" $ACMEIDUpdate = Update-ACMEIdentifier $ACMEAlias $ACMEIDStatus = $ACMEIDUpdate.Status if ($ACMEIDStatus -eq "valid") { write-host "Validation OK" -foregroundcolor green } else { write-host "Error: Validation failed for alias $ACMEAlias" -foregroundcolor red exit } } #Prepare and submit the certificate $SANAlias = "SAN" + "$CurrentDate" $NewCert = New-ACMECertificate $SubjectAlias -Generate -AlternativeIdentifierRefs $ACMEAliasArray -Alias $SANAlias $SubmitNewCert = Submit-ACMECertificate $SANAlias #Wait until the certificate has been issued write-host "wait 30 Seconds..." sleep -seconds 30 #Check status write-host "Check the certificate..." $UpdateNewCert = Update-ACMECertificate $SANAlias $CertStatus = (Get-ACMECertificate $SANAlias).CertificateRequest.Statuscode sleep 5 if ($CertStatus -match "OK") { write-host "Certificate OK" -foregroundcolor green } else { write-host "Error: Certificate not issued" -foregroundcolor red exit } #Export the certificate from Vault and assign Exchange write-host "Export the certificate to $env:temp" $CertPath = "$env:temp" + "\" + "$SANAlias" + ".pfx" $PFXPasswort = Get-Random -Minimum 1000000 -Maximum 9999999 $CertExport = Get-ACMECertificate $SANAlias -ExportPkcs12 $CertPath -CertificatePassword $PFXPasswort write-host "Check whether the certificate has been exported..." if (test-path $CertPath) { write-host "Certificate successfully exported" -foregroundcolor green write-host "Password for the PFX file: $PFXPasswort" } else { write-host "Error: The certificate was not exported" -foregroundcolor red exit } write-host "Exchange certificate is assigned and activated" $ImportPassword = ConvertTo-SecureString -String $PFXPasswort -Force –AsPlainText if ($ExchangeVersion -match "Version 15") { Import-ExchangeCertificate -FileName $CertPath -FriendlyName $ExchangeSubject -Password $ImportPassword -PrivateKeyExportable:$true | Enable-ExchangeCertificate -Services "SMTP, IMAP, POP, IIS" –force } elseif ($ExchangeVersion -match "Version 14") { Import-ExchangeCertificate -FileData ([Byte[]]$(Get-Content -Path $CertPath -Encoding byte -ReadCount 0)) -FriendlyName $ExchangeSubject -Password $ImportPassword -PrivateKeyExportable:$true | Enable-ExchangeCertificate -Services "SMTP, IMAP, POP, IIS" -force } write-host "Check whether the certificate has been activated" $CurrentCertThumbprint = (Get-ChildItem -Path IIS:SSLBindings | where {$_.port -match "443" -and $_.IPAddress -match "0.0.0.0" } | select Thumbprint).Thumbprint $ExportThumbprint = $CertExport.Thumbprint if ($CurrentCertThumbprint -eq $ExportThumbprint) { write-host "The certificate has been successfully activated" -foregroundcolor green } else { write-host "Activation failed" -foregroundcolor red exit } } #---------------------------------------Renewal------------------------------------------------ #Automatic Renewal if ($renew -eq $True) { $PFXPasswort = Get-Random -Minimum 1000000 -Maximum 9999999 $CurrentCertThumbprint = (Get-ChildItem -Path IIS:SSLBindings | where {$_.port -match "443" -and $_.IPAddress -match "0.0.0.0" } | select Thumbprint).Thumbprint $ExchangeCertificate = Get-ExchangeCertificate -Thumbprint $CurrentCertThumbprint $ExchangeSANs = ($ExchangeCertificate.CertificateDomains).Address $ExchangeSubject = $ExchangeCertificate.Subject.Replace("CN=","") if ($ExchangeSANs -notcontains $ExchangeSubject) {$ExchangeSANs += $ExchangeSubject} $CurrentDate = get-date $VaildTill = $ExchangeCertificate.NotAfter $DaysLeft = ($VaildTill - $CurrentDate).Days if ($DaysLeft -le 4) #Renew 4 days before expiration { $ExchangeSANID = 1 foreach ($ExchangeSAN in $ExchangeSANs) { $CurrentDate = get-date -format ddMMyyyy $ACMEAlias = "Cert" + "$CurrentDate" + "-" + "$ExchangeSANID" $ExchangeSANID++ New-ACMEIdentifier -Dns $ExchangeSAN -Alias $ACMEAlias Complete-ACMEChallenge $ACMEAlias -ChallengeType http-01 -Handler iis -HandlerParameters @{ WebSiteRef = 'Default Web Site' } [Array]$ACMEAliasArray += $ACMEAlias if ($ExchangeSAN -match $ExchangeSubject) {$ExchangeSubjectAlias = $ACMEAlias} } foreach ($ACMEAlias in $ACMEAliasArray) { Submit-ACMEChallenge $ACMEAlias -ChallengeType http-01 } sleep -seconds 30 foreach ($ACMEAlias in $ACMEAliasArray) { Update-ACMEIdentifier $ACMEAlias } $SANAlias = "SAN" + "$CurrentDate" New-ACMECertificate $ExchangeSubjectAlias -Generate -AlternativeIdentifierRefs $ACMEAliasArray -Alias $SANAlias Submit-ACMECertificate $SANAlias sleep -seconds 30 Update-ACMECertificate $SANAlias $CertPath = "$env:temp" + "\" + "$SANAlias" + ".pfx" $CertExport = Get-ACMECertificate $SANAlias -ExportPkcs12 $CertPath -CertificatePassword $PFXPasswort $ImportPassword = ConvertTo-SecureString -String $PFXPasswort -Force –AsPlainText if ($ExchangeVersion -match "Version 15") { Import-ExchangeCertificate -FileName $CertPath -FriendlyName $ExchangeSubject -Password $ImportPassword -PrivateKeyExportable:$true | Enable-ExchangeCertificate -Services "SMTP, IMAP, POP, IIS" –force } elseif ($ExchangeVersion -match "Version 14") { Import-ExchangeCertificate -FileData ([Byte[]]$(Get-Content -Path $CertPath -Encoding byte -ReadCount 0)) -FriendlyName $ExchangeSubject -Password $ImportPassword -PrivateKeyExportable:$true | Enable-ExchangeCertificate -Services "SMTP, IMAP, POP, IIS" -force } } } }
Hi Edwin, Fantastic script! Do you have one for Lync/Skype for business certificates? I would guess its not too hard to modify this one!
ReplyDeleteHi.
ReplyDeleteI got Error: Validation failed for alias Cert201120171015-1 when I run the script on my Exchange 2016 server in my lab. The server runs on Server 2016.
Hi Glenn, I suggest you contact Frank as he's the creator of the script and loves to tweak his scripts.
DeleteI'm getting suck on the auto-renew...
ReplyDelete-Command "&'C:\Users\kevin\Downloads\PowerShell\CertificateAssistant.ps1' -renew:$true"
Is what the Task Scheduler does..
So I ran it to see where it gets stuck..
Add additional DNS names to the certificate? (y/n):
Does it do the entire process over again? Or is there a way to get it to just renew the certificate it has already done? I have a few alternate DNS-Names in my script..
Does Frank communicate in English? The only English phrase I saw on his site was the link here. ;)
Cheers,
Kevin