12 October 2017

Enable MFA for Exchange Online and Outlook, Skype Online and the Skype client

For the Office 365 services, the default state of modern authentication is:

  • Exchange Online - off by default
  • Skype Online - off by default 
  • SharePoint Online - on by default
This means you have to enable it for Exchange Online and Skype Online after enabling MFA for your users.
Here how:

For Exchange Online:

Connect to Exchange Online PowerShell as shown here.
Do one of these steps:
  1. Run this command to enable modern authentication in Exchange Online:
    Set-OrganizationConfig -OAuth2ClientProfileEnabled $true
  2. Run this command to disable modern authentication in Exchange Online:
    Set-OrganizationConfig -OAuth2ClientProfileEnabled $false
  3. To verify that the change was successful, run this command:
    Get-OrganizationConfig | Format-Table -Auto Name,OAuth*

For Skype Online:


Connect to Skype for Business Online using remote PowerShell: https://aka.ms/SkypePowerShell
Run the following command:
  1. Run this command to enable modern authentication in Skype Online:
    Set-CsOAuthConfiguration -ClientAdalAuthOverride Allowed
  2. Verify that the change was successful by running the following:
    get-CsOAuthConfiguration | select ClientAdalAuthOverride
The output for both will look like this:

Get-OrganizationConfig | Select OAuth2ClientProfileEnabled

OAuth2ClientProfileEnabled
--------------------------
                     False

Set-OrganizationConfig -OAuth2ClientProfileEnabled $True


Get-OrganizationConfig | Select OAuth2ClientProfileEnabled

OAuth2ClientProfileEnabled
--------------------------
                      True

Get-CsOAuthConfiguration | Select ClientAdalAuthOverride

ClientAdalAuthOverride
----------------------
Disallowed

Set-CsOAuthConfiguration -ClientAdalAuthOverride Allowed
Get-CsOAuthConfiguration | Select ClientAdalAuthOverride

ClientAdalAuthOverride
----------------------
Allowed

19 September 2017

Exchange 2013 installing a CU, Schema update required or not?

Update 03-10-2017 - Technet article quote added

From Technet:
Active Directory schema changes in Exchange 2013 cumulative updates CU8 and later

No changes have been made to the Active Directory schema in Exchange 2013 from CU8 onwards. The last cumulative update to include schema changes is currently Exchange 2013 CU7.https://technet.microsoft.com/en-us/library/bb738144(v=exchg.150).aspx


This is some of those things you need to check before updating an Exchange environment every time a new CU gets put out.

Do i need to do a Schema update or not?

I came across this post from Rhoderick Milne,

Table Of Exchange 2013 Schema Versions
Exchange Version
msExchProductId
rangeUpper
MESO objectVersion
Organisation objectVersion
Exchange 2013 RTM
15.00.0516.032
15137
13236
15449
Exchange 2013 CU1
15.00.0620.029
15254
13236
15614
Exchange 2013 CU2
15.00.0712.024
15281
13236
15688
Exchange 2013 CU3
15.00.0775.038
15283
13236
15763
Exchange 2013 SP1
15.00.0847.032
15292
13236
15844
Exchange 2013 CU5
15.00.0913.022
15300
13236
15870
Exchange 2013 CU6
15.00.0995.029
15303
13236
15965
Exchange 2013 CU7
15.00.1044.025
15312
13236
15965
Exchange 2013 CU8
15.00.1076.009
15312
13236
15965
Exchange 2013 CU9
15.00.1104.005
15312
13236
15965
Exchange 2013 CU10
15.00.1130.007
15312
13236
16130
Exchange 2013 CU11
15.00.1156.006
15312
13236
16130
Exchange 2013 CU12
15.00.1178.004
15312
13236
16130
Exchange 2013 CU13
15.00.1210.003
15312
13236
16130
Exchange 2013 CU14
15.00.1236.003
15312
13236
16130
Exchange 2013 CU15
15.00.1263.005
15312
13236
16130
Exchange 2013 CU16+.NET4.6.2
15.00.1293.002
15312
13236
16130
Exchange 2013 CU17
15.00.1320.004
15312
13236
16130
Exchange 2013 CU18
15.00. 1347.002
15312
13236
16130

TechNet documents the expected values for the various Exchange 2013 objects in AD.

Check back here when a new CU is released.

Another top tip from Rhoderick is to install .net 4.6.1 after installing the CU and having the machine rebooted.
At the moment of writing .NET 4.7 is not supported for Exchange 2013 and Exchange 2016 no matter what CU you're on.

14 September 2017

LAPS - Restore machine from backup - Password not in AD

Updated 19-09-2017

LAPS short for Local Administrator Password Solution tool is a great way to secure your local administrator accounts on servers.
But there is one problem with the tool.
Password's that have been changed by LAPS are stored in AD, but only for 30 days. After 30 days it's automatically changed and overwritten. So when you use the LAPS tool or check the AD attribute, the password that you see there is valid at that time, the one from 100 days ago isn't there.

But what if you needed to restore a machine that is 31 days old or 40 days, heck even 100 days.

There is no way to find the password for the local admin account, because every 30 days it gets changed and overwritten in AD by LAPS.

Now this is where this script comes in.
The idea is really simple and there's a draw back with it at the same time.

The script exports all the passwords from all servers from a specific OU, every week or month, you decide, and writes a password protected zip file on a file share or sends it to an email address of your choice.

I can hear you think, that's a nice little security issue you just created there.
I know, I only provide the tool, it's up to you whether you want to use it or not.
I've seen someone say somewhere that you could use ADExplorer to export the AD to keep the passwords from longer than 30 days ago stored somewhere.
If you think that's a good idea you can go ahead and use that.

To give you an idea, the output looks like this:







Now on your fileshare there is a zip file protected by a password that you store some where (in Keepass for instance). In case of an emergency you unzip the file and browse the html file for the servername and the associated administrator password.
Advantage is that this is a little more secure than just dumping all the passwords in a mailbox.

On the otherhand if you bury this deep in a mailbox and create a scheduled task that runs every 30 days, you have every password ever changed by LAPS right in your mailbox.

And if your worried about your security, it's local admin accounts not domain admin accounts.
So you're pretty safe.

Update:
Wanted to have the passwords written in a password protected zip file on a file share:
Here's the script or download from here
Requires the ActiveDirectory and LAPS admpwd.ps1 modules to be installed.

PS: Remove the spaces before and after "< style > and </ style >"
##############################################################################                        
## Get-PWDOverviewMonthly                        
## Purpose: Sends report on the passwords set by LAPS                        
## Author: Edwin van Brenk                        
## Date: 19 september 2017                        
## Version: 1.1                        
##############################################################################                        
#Load Modules            
Import-module activedirectory            
Import-Module AdmPwd*            
            
# Password for the ZIP file            
#            
# ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION-ATTENTION            
#            
# Use the section below to generate an encrypted password file for the zip file            
# Needs to be run only at initial script setup            
# Uncomment before use, comment after use            
#            
# Type your password            
#$password = Read-Host "Password" -AsSecureString            
            
# Write to file            
#$encrypted = ConvertFrom-SecureString -SecureString $password | Set-Content "C:\Scripts\Get-LAPSOverviewMonthly\Password.txt"            
            
# Get password            
$secured = ConvertTo-SecureString -String $(Get-Content C:\Scripts\Get-LAPSOverviewMonthly\Password.txt)            
            
# Do password stuff            
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "userid", $secured            
            
# Zip function            
function Write-ZipUsing7Zip([string]$FilesToZip, [string]$ZipOutputFilePath, [string]$Password, [ValidateSet('7z','zip','gzip','bzip2','tar','iso','udf')][string]$CompressionType = 'zip', [switch]$HideWindow)            
{            
 # Look for the 7zip executable.            
 $pathTo32Bit7Zip = "C:\Program Files (x86)\7-Zip\7z.exe"            
 $pathTo64Bit7Zip = "C:\Program Files\7-Zip\7z.exe"            
 $THIS_SCRIPTS_DIRECTORY = Split-Path $script:MyInvocation.MyCommand.Path            
 $pathToStandAloneExe = Join-Path $THIS_SCRIPTS_DIRECTORY "7za.exe"            
 if (Test-Path $pathTo64Bit7Zip) { $pathTo7ZipExe = $pathTo64Bit7Zip }             
 elseif (Test-Path $pathTo32Bit7Zip) { $pathTo7ZipExe = $pathTo32Bit7Zip }            
 elseif (Test-Path $pathToStandAloneExe) { $pathTo7ZipExe = $pathToStandAloneExe }            
 else { throw "Could not find the 7-zip executable." }            
             
 # Delete the destination zip file if it already exists (i.e. overwrite it).            
 if (Test-Path $ZipOutputFilePath) { Remove-Item $ZipOutputFilePath -Force }            
             
 $windowStyle = "Normal"            
 if ($HideWindow) { $windowStyle = "Hidden" }            
             
 # Create the arguments to use to zip up the files.            
 # Command-line argument syntax can be found at: http://www.dotnetperls.com/7-zip-examples            
 $arguments = "a -t$CompressionType ""$ZipOutputFilePath"" ""$FilesToZip"" -mx9"            
 if (!([string]::IsNullOrEmpty($Password))) { $arguments += " -p$Password" }            
             
 # Zip up the files.            
 $p = Start-Process $pathTo7ZipExe -ArgumentList $arguments -Wait -PassThru -WindowStyle $windowStyle            
            
 # If the files were not zipped successfully.            
 if (!(($p.HasExited -eq $true) -and ($p.ExitCode -eq 0)))             
 {            
  throw "There was a problem creating the zip file '$ZipFilePath'."            
 }            
}            
            
# Various Settings            
$Date = Get-Date -Format dd-MM-yyyy             
$reportPath = "\\domain.lan\Fileshare\"            
            
# Build table for html files, remove the spaces before and after "< style > and </ style >"                    
$style = "< style >BODY{font-family: Arial; font-size: 10pt;}"                        
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"                        
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"                        
$style = $style + "TD{border: 1px solid black; padding: 5px; }"                        
$style = $style + "</ style >"                        
# End HTML Output file style   
# Get all computers in OU, convert output to HTML table $pwd=Get-ADComputer -Filter * -SearchBase "ou=servers,ou=systems,dc=domain,dc=lan" | Get-AdmPwdPassword -ComputerName {$_.Name} | ConvertTo-HTML -Head $style | out-file $reportPath\Report.html # Zip HTML file Write-ZipUsing7Zip -FilesToZip "$reportPath\Report.html" -ZipOutputFilePath "$reportPath\Report-$Date.zip" -Password $cred.GetNetworkCredential().Password -HideWindow # Delete temporary html file Remove-Item $reportPath\Report.html

Here's the script or download from here
Requires the ActiveDirectory and LAPS admpwd.ps1 modules to be installed.
PS: Remove the spaces before and after "< style > and </ style >"
##############################################################################                        
## Get-PWDOverviewMonthly                        
## Purpose: Sends report on the passwords set by LAPS                        
## Author: Edwin van Brenk                        
## Date: 14 september 2017                        
## Version: 1.0                        
##############################################################################                        
#Load Modules            
Import-module activedirectory            
Import-Module AdmPwd*            
            
$Date = Get-Date -Format dd-MM-yyyy             
            
#SMTP options for sending the report email                        
$smtpServer = "smtp.domain.lan"                        
$smtpFrom = "LAPS@domain.lan"                        
$smtpTo = "edwin@domain.lan"                        
$Subject = "LAPS monthly overview $Date"            
            
# Build table for html files, remove the spaces before and after "< style > and </ style >"                    
$style = "< style >BODY{font-family: Arial; font-size: 10pt;}"                        
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"                        
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"                        
$style = $style + "TD{border: 1px solid black; padding: 5px; }"                        
$style = $style + "</ style >"                        
# End HTML Output file style              
            
# Get all computers in OU, convert output to HTML table            
$pwd=Get-ADComputer -Filter * -SearchBase "ou=servers,ou=systems,dc=domain,dc=lan" | Get-AdmPwdPassword -ComputerName {$_.Name} | ConvertTo-HTML -Head $style            
            
# Send email            
Send-MailMessage -To $smtpTo -From $smtpFrom -SmtpServer $smtpServer -Subject $Subject -Body ($pwd | out-string) -BodyAsHtml            


12 September 2017

Exchange Management Shell not connecting to the server - PowerShell Exchange 2013 - EMS

PSSession : [sr-xxxxx.domain.lan] Processing data from remote server sr-xxxxx.domain.lan failed with the following error message: [ClientAccessServer=SR-xxxxx,BackEndServer=sr-xxxxx.domain.lan,RequestId=8aa81a77-bea6-408f-a4c6-83657ecc222f,TimeStamp=6-9-2017 19:29:58] [FailureCategory=WSMan-Others] The EndpointConfiguration with the http://schemas.microsoft.com/powershell/microsoft.exchange identifier is not in a valid initial session state on the remote computer. Contact your Windows PowerShell administrator, or the owner or creator of the endpoint configurat
ion. For more information, see the about_Remote_Troubleshooting Help topic.
At line:1 char:12
+ $session = New-PSSession -ConfigurationName microsoft.exchange -Conne ...
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotingTransportException
    + FullyQualifiedErrorId : IncorrectProtocolVersion,PSSessionOpenFailed





Well, that's a nice one isn't it?

Problem is that on the local Exchange server you cannot start the management shell to administer your Exchange server. It does not want to connect to itself.
And by that I mean to the server name and domain name.

You can however start a PowerShell console and import the exchange module, this will load up and you can do your Exchange administration stuff.
But remoting is out of the question.

Now check this:

Event ID: 2112
The server doesn't have the Audit Security privilege on a domain controller. This privilege is used by ADAccess. Run policytest.exe. See KB 314294


The problem is that there is a group missing in the default domain controller policy, and notice the words "domain controller". Or the group is missing in a hardening policy that you created yourself for the domain controllers. The group we're looking for is "Exchange Servers".

Open the Microsoft Management Console and add the Group Policy Management Editor snap-in. Then, click Browse and select Domain Controllers from the Domains, OUs and linked Group Policy Objects list. Click OK twice and then click Finish. Click OK to close the Add or Remove Snap-ins window.

In the console tree, expand Local Computer Policy, Windows Settings, Security Settings and Local Policies. Under Local Policies, click User Rights Assignments.

In the results pane, double-click Manage auditing and security log. Verify that the "Exchange Servers" group is listed.


Make sure that the Exchange server is still a member of the Exchange Domain Servers group.
Make sure that the group permissions are inherited by the Microsoft Exchange computer account.

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            
      }            
    }            
  }            
}