09 September 2017

New version of the Exchange Certificate Wizard for Let's Encrypt in English by Franky's web

I think Let's encrypt is the best initiative I've seen in a long while on the internet.
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            
      }            
    }            
  }            
}            

4 comments:

  1. 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!

    ReplyDelete
  2. Hi.
    I 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.

    ReplyDelete
    Replies
    1. Hi Glenn, I suggest you contact Frank as he's the creator of the script and loves to tweak his scripts.

      Delete
  3. I'm getting suck on the auto-renew...

    -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

    ReplyDelete