PasswordSolution.psm1

function ConvertFrom-DistinguishedName { 
    <#
    .SYNOPSIS
    Converts a Distinguished Name to CN, OU, Multiple OUs or DC
 
    .DESCRIPTION
    Converts a Distinguished Name to CN, OU, Multiple OUs or DC
 
    .PARAMETER DistinguishedName
    Distinguished Name to convert
 
    .PARAMETER ToOrganizationalUnit
    Converts DistinguishedName to Organizational Unit
 
    .PARAMETER ToDC
    Converts DistinguishedName to DC
 
    .PARAMETER ToDomainCN
    Converts DistinguishedName to Domain CN
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit
 
    Output:
    OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName
 
    Output:
    Przemyslaw Klys
 
    .EXAMPLE
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit -IncludeParent
 
    Output:
    OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz
    OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit
 
    Output:
    OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    $Con = @(
        'CN=Windows Authorization Access Group,CN=Builtin,DC=ad,DC=evotec,DC=xyz'
        'CN=Mmm,DC=elo,CN=nee,DC=RootDNSServers,CN=MicrosoftDNS,CN=System,DC=ad,DC=evotec,DC=xyz'
        'CN=e6d5fd00-385d-4e65-b02d-9da3493ed850,CN=Operations,CN=DomainUpdates,CN=System,DC=ad,DC=evotec,DC=xyz'
        'OU=Domain Controllers,DC=ad,DC=evotec,DC=pl'
        'OU=Microsoft Exchange Security Groups,DC=ad,DC=evotec,DC=xyz'
    )
 
    ConvertFrom-DistinguishedName -DistinguishedName $Con -ToLastName
 
    Output:
    Windows Authorization Access Group
    Mmm
    e6d5fd00-385d-4e65-b02d-9da3493ed850
    Domain Controllers
    Microsoft Exchange Security Groups
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param([Parameter(ParameterSetName = 'ToOrganizationalUnit')]
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')]
        [Parameter(ParameterSetName = 'ToDC')]
        [Parameter(ParameterSetName = 'ToDomainCN')]
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'ToLastName')]
        [alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName,
        [Parameter(ParameterSetName = 'ToOrganizationalUnit')][switch] $ToOrganizationalUnit,
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][alias('ToMultipleOU')][switch] $ToMultipleOrganizationalUnit,
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][switch] $IncludeParent,
        [Parameter(ParameterSetName = 'ToDC')][switch] $ToDC,
        [Parameter(ParameterSetName = 'ToDomainCN')][switch] $ToDomainCN,
        [Parameter(ParameterSetName = 'ToLastName')][switch] $ToLastName)
    Process {
        foreach ($Distinguished in $DistinguishedName) {
            if ($ToDomainCN) {
                $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                $CN = $DN -replace ',DC=', '.' -replace "DC="
                if ($CN) { $CN }
            } elseif ($ToOrganizationalUnit) {
                $Value = [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value
                if ($Value) { $Value }
            } elseif ($ToMultipleOrganizationalUnit) {
                if ($IncludeParent) { $Distinguished }
                while ($true) {
                    $Distinguished = $Distinguished -replace '^.+?,(?=..=)'
                    if ($Distinguished -match '^DC=') { break }
                    $Distinguished
                }
            } elseif ($ToDC) {
                $Value = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                if ($Value) { $Value }
            } elseif ($ToLastName) {
                $NewDN = $Distinguished -split ",DC="
                if ($NewDN[0].Contains(",OU=")) { [Array] $ChangedDN = $NewDN[0] -split ",OU=" } elseif ($NewDN[0].Contains(",CN=")) { [Array] $ChangedDN = $NewDN[0] -split ",CN=" } else { [Array] $ChangedDN = $NewDN[0] }
                if ($ChangedDN[0].StartsWith('CN=')) { $ChangedDN[0] -replace 'CN=', '' } else { $ChangedDN[0] -replace 'OU=', '' }
            } else {
                $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'
                $Found = $Distinguished -match $Regex
                if ($Found) { $Matches.cn }
            }
        }
    }
}
function Convert-UserAccountControl { 
    [cmdletBinding()]
    param([alias('UAC')][int] $UserAccountControl,
        [string] $Separator)
    $UserAccount = [ordered] @{"SCRIPT"  = 1
        "ACCOUNTDISABLE"                 = 2
        "HOMEDIR_REQUIRED"               = 8
        "LOCKOUT"                        = 16
        "PASSWD_NOTREQD"                 = 32
        "ENCRYPTED_TEXT_PWD_ALLOWED"     = 128
        "TEMP_DUPLICATE_ACCOUNT"         = 256
        "NORMAL_ACCOUNT"                 = 512
        "INTERDOMAIN_TRUST_ACCOUNT"      = 2048
        "WORKSTATION_TRUST_ACCOUNT"      = 4096
        "SERVER_TRUST_ACCOUNT"           = 8192
        "DONT_EXPIRE_PASSWORD"           = 65536
        "MNS_LOGON_ACCOUNT"              = 131072
        "SMARTCARD_REQUIRED"             = 262144
        "TRUSTED_FOR_DELEGATION"         = 524288
        "NOT_DELEGATED"                  = 1048576
        "USE_DES_KEY_ONLY"               = 2097152
        "DONT_REQ_PREAUTH"               = 4194304
        "PASSWORD_EXPIRED"               = 8388608
        "TRUSTED_TO_AUTH_FOR_DELEGATION" = 16777216
        "PARTIAL_SECRETS_ACCOUNT"        = 67108864
    }
    $Output = foreach ($_ in $UserAccount.Keys) {
        $binaryAnd = $UserAccount[$_] -band $UserAccountControl
        if ($binaryAnd -ne "0") { $_ }
    }
    if ($Separator) { $Output -join $Separator } else { $Output }
}
function Get-GitHubLatestRelease { 
    <#
    .SYNOPSIS
    Gets one or more releases from GitHub repository
 
    .DESCRIPTION
    Gets one or more releases from GitHub repository
 
    .PARAMETER Url
    Url to github repository
 
    .EXAMPLE
    Get-GitHubLatestRelease -Url "https://api.github.com1/repos/evotecit/Testimo/releases" | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdLetBinding()]
    param([parameter(Mandatory)][alias('ReleasesUrl')][uri] $Url)
    $ProgressPreference = 'SilentlyContinue'
    $Responds = Test-Connection -ComputerName $URl.Host -Quiet -Count 1
    if ($Responds) {
        Try {
            [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json)
            foreach ($JsonContent in $JsonOutput) {
                [PSCustomObject] @{PublishDate = [DateTime] $JsonContent.published_at
                    CreatedDate                = [DateTime] $JsonContent.created_at
                    PreRelease                 = [bool] $JsonContent.prerelease
                    Version                    = [version] ($JsonContent.name -replace 'v', '')
                    Tag                        = $JsonContent.tag_name
                    Branch                     = $JsonContent.target_commitish
                    Errors                     = ''
                }
            }
        } catch {
            [PSCustomObject] @{PublishDate = $null
                CreatedDate                = $null
                PreRelease                 = $null
                Version                    = $null
                Tag                        = $null
                Branch                     = $null
                Errors                     = $_.Exception.Message
            }
        }
    } else {
        [PSCustomObject] @{PublishDate = $null
            CreatedDate                = $null
            PreRelease                 = $null
            Version                    = $null
            Tag                        = $null
            Branch                     = $null
            Errors                     = "No connection (ping) to $($Url.Host)"
        }
    }
    $ProgressPreference = 'Continue'
}
function Get-WinADForestDetails { 
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [string] $Filter = '*',
        [switch] $TestAvailability,
        [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All',
        [int[]] $Ports = 135,
        [int] $PortsTimeout = 100,
        [int] $PingCount = 1,
        [switch] $Extended,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    if ($Global:ProgressPreference -ne 'SilentlyContinue') {
        $TemporaryProgress = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
    }
    if (-not $ExtendedForestInformation) {
        $Findings = [ordered] @{}
        try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch {
            Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)"
            return
        }
        if (-not $ForestInformation) { return }
        $Findings['Forest'] = $ForestInformation
        $Findings['ForestDomainControllers'] = @()
        $Findings['QueryServers'] = @{}
        $Findings['DomainDomainControllers'] = @{}
        [Array] $Findings['Domains'] = foreach ($Domain in $ForestInformation.Domains) {
            if ($IncludeDomains) {
                if ($Domain -in $IncludeDomains) { $Domain.ToLower() }
                continue
            }
            if ($Domain -notin $ExcludeDomains) { $Domain.ToLower() }
        }
        [Array] $DomainsActive = foreach ($Domain in $Findings['Forest'].Domains) {
            try {
                $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop
                $OrderedDC = [ordered] @{Domain = $DC.Domain
                    Forest                      = $DC.Forest
                    HostName                    = [Array] $DC.HostName
                    IPv4Address                 = $DC.IPv4Address
                    IPv6Address                 = $DC.IPv6Address
                    Name                        = $DC.Name
                    Site                        = $DC.Site
                }
            } catch {
                Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)"
                continue
            }
            if ($Domain -eq $Findings['Forest']['Name']) { $Findings['QueryServers']['Forest'] = $OrderedDC }
            $Findings['QueryServers']["$Domain"] = $OrderedDC
            $Domain
        }
        [Array] $Findings['Domains'] = foreach ($Domain in $Findings['Domains']) {
            if ($Domain -notin $DomainsActive) {
                Write-Warning "Get-WinADForestDetails - Domain $Domain doesn't seem to be active (no DCs). Skipping."
                continue
            }
            $Domain
        }
        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0]
            [Array] $AllDC = try {
                try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop } catch {
                    Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)"
                    continue
                }
                foreach ($S in $DomainControllers) {
                    if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } }
                    if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } }
                    $Server = [ordered] @{Domain = $Domain
                        HostName                 = $S.HostName
                        Name                     = $S.Name
                        Forest                   = $ForestInformation.RootDomain
                        Site                     = $S.Site
                        IPV4Address              = $S.IPV4Address
                        IPV6Address              = $S.IPV6Address
                        IsGlobalCatalog          = $S.IsGlobalCatalog
                        IsReadOnly               = $S.IsReadOnly
                        IsSchemaMaster           = ($S.OperationMasterRoles -contains 'SchemaMaster')
                        IsDomainNamingMaster     = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                        IsPDC                    = ($S.OperationMasterRoles -contains 'PDCEmulator')
                        IsRIDMaster              = ($S.OperationMasterRoles -contains 'RIDMaster')
                        IsInfrastructureMaster   = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                        OperatingSystem          = $S.OperatingSystem
                        OperatingSystemVersion   = $S.OperatingSystemVersion
                        OperatingSystemLong      = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion
                        LdapPort                 = $S.LdapPort
                        SslPort                  = $S.SslPort
                        DistinguishedName        = $S.ComputerObjectDN
                        Pingable                 = $null
                        WinRM                    = $null
                        PortOpen                 = $null
                        Comment                  = ''
                    }
                    if ($TestAvailability) {
                        if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount }
                        if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status }
                        if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status }
                    }
                    [PSCustomObject] $Server
                }
            } catch {
                [PSCustomObject]@{Domain     = $Domain
                    HostName                 = ''
                    Name                     = ''
                    Forest                   = $ForestInformation.RootDomain
                    IPV4Address              = ''
                    IPV6Address              = ''
                    IsGlobalCatalog          = ''
                    IsReadOnly               = ''
                    Site                     = ''
                    SchemaMaster             = $false
                    DomainNamingMasterMaster = $false
                    PDCEmulator              = $false
                    RIDMaster                = $false
                    InfrastructureMaster     = $false
                    LdapPort                 = ''
                    SslPort                  = ''
                    DistinguishedName        = ''
                    Pingable                 = $null
                    WinRM                    = $null
                    PortOpen                 = $null
                    Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                }
            }
            if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC }
            [Array] $Findings['DomainDomainControllers'][$Domain]
        }
        if ($Extended) {
            $Findings['DomainsExtended'] = @{}
            $Findings['DomainsExtendedNetBIOS'] = @{}
            foreach ($DomainEx in $Findings['Domains']) {
                try {
                    $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object { [ordered] @{AllowedDNSSuffixes = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ }
                            ChildDomains                                                                                                                                              = $_.ChildDomains | ForEach-Object -Process { $_ }
                            ComputersContainer                                                                                                                                        = $_.ComputersContainer
                            DeletedObjectsContainer                                                                                                                                   = $_.DeletedObjectsContainer
                            DistinguishedName                                                                                                                                         = $_.DistinguishedName
                            DNSRoot                                                                                                                                                   = $_.DNSRoot
                            DomainControllersContainer                                                                                                                                = $_.DomainControllersContainer
                            DomainMode                                                                                                                                                = $_.DomainMode
                            DomainSID                                                                                                                                                 = $_.DomainSID.Value
                            ForeignSecurityPrincipalsContainer                                                                                                                        = $_.ForeignSecurityPrincipalsContainer
                            Forest                                                                                                                                                    = $_.Forest
                            InfrastructureMaster                                                                                                                                      = $_.InfrastructureMaster
                            LastLogonReplicationInterval                                                                                                                              = $_.LastLogonReplicationInterval
                            LinkedGroupPolicyObjects                                                                                                                                  = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ }
                            LostAndFoundContainer                                                                                                                                     = $_.LostAndFoundContainer
                            ManagedBy                                                                                                                                                 = $_.ManagedBy
                            Name                                                                                                                                                      = $_.Name
                            NetBIOSName                                                                                                                                               = $_.NetBIOSName
                            ObjectClass                                                                                                                                               = $_.ObjectClass
                            ObjectGUID                                                                                                                                                = $_.ObjectGUID
                            ParentDomain                                                                                                                                              = $_.ParentDomain
                            PDCEmulator                                                                                                                                               = $_.PDCEmulator
                            PublicKeyRequiredPasswordRolling                                                                                                                          = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ }
                            QuotasContainer                                                                                                                                           = $_.QuotasContainer
                            ReadOnlyReplicaDirectoryServers                                                                                                                           = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ }
                            ReplicaDirectoryServers                                                                                                                                   = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ }
                            RIDMaster                                                                                                                                                 = $_.RIDMaster
                            SubordinateReferences                                                                                                                                     = $_.SubordinateReferences | ForEach-Object -Process { $_ }
                            SystemsContainer                                                                                                                                          = $_.SystemsContainer
                            UsersContainer                                                                                                                                            = $_.UsersContainer
                        } }
                    $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName']
                    $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx]
                } catch {
                    Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)"
                    continue
                }
            }
        }
        if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress }
        $Findings
    } else {
        $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation
        [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) {
            if ($IncludeDomains) {
                if ($_ -in $IncludeDomains) { $_.ToLower() }
                continue
            }
            if ($_ -notin $ExcludeDomains) { $_.ToLower() }
        }
        foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainDomainControllers.Remove($_) } }
        foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) {
            if ($_ -notin $Findings.Domains) {
                $Findings.DomainsExtended.Remove($_)
                $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName'
                if ($NetBiosName) { $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName) }
            }
        }
        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) {
                if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } }
                if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } }
                $S
            }
            if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC }
            [Array] $Findings['DomainDomainControllers'][$Domain]
        }
        $Findings
    }
}
function Start-TimeLog { 
    [CmdletBinding()]
    param()
    [System.Diagnostics.Stopwatch]::StartNew()
}
function Stop-TimeLog { 
    [CmdletBinding()]
    param ([Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time,
        [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner',
        [switch] $Continue)
    Begin {}
    Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } }
    End {
        if (-not $Continue) { $Time.Stop() }
        return $TimeToExecute
    }
}
function Write-Color { 
    <#
    .SYNOPSIS
        Write-Color is a wrapper around Write-Host.
 
        It provides:
        - Easy manipulation of colors,
        - Logging output to file (log)
        - Nice formatting options out of the box.
 
    .DESCRIPTION
        Author: przemyslaw.klys at evotec.pl
        Project website: https://evotec.xyz/hub/scripts/write-color-ps1/
        Project support: https://github.com/EvotecIT/PSWriteColor
 
        Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    # Added in 0.5
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    wc -t "my text" -c yellow -b green
    wc -text "my text" -c red
 
    .NOTES
        Additional Notes:
        - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param ([alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [int] $LogRetry = 2,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine)
    $DefaultColor = $Color[0]
    if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) {
        Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."
        return
    }
    if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } }
    if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } }
    if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline }
    if ($Text.Count -ne 0) {
        if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } } } else {
            if ($null -eq $BackGroundColor) {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline }
            } else {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline }
            }
        }
    }
    if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host }
    if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($Text.Count -and $LogFile) {
        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] }
        $Saved = $false
        $Retry = 0
        Do {
            $Retry++
            try {
                if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false }
                $Saved = $true
            } catch { if ($Saved -eq $false -and $Retry -eq $LogRetry) { $PSCmdlet.WriteError($_) } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } }
        } Until ($Saved -eq $true -or $Retry -ge $LogRetry)
    }
}
function ConvertTo-OperatingSystem { 
    <#
    .SYNOPSIS
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .DESCRIPTION
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .PARAMETER OperatingSystem
    Operating System as returned by Active Directory
 
    .PARAMETER OperatingSystemVersion
    Operating System Version as returned by Active Directory
 
    .EXAMPLE
    $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object {
        $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
        Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force
        $_
    }
    $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table
 
    .EXAMPLE
    $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $OperatingSystem,
        [string] $OperatingSystemVersion)
    if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') {
        $Systems = @{'10.0 (22000)' = 'Windows 11 21H2'
            '10.0 (19043)'          = 'Windows 10 21H1'
            '10.0 (19042)'          = 'Windows 10 20H2'
            '10.0 (19041)'          = 'Windows 10 2004'
            '10.0 (18898)'          = 'Windows 10 Insider Preview'
            '10.0 (18363)'          = "Windows 10 1909"
            '10.0 (18362)'          = "Windows 10 1903"
            '10.0 (17763)'          = "Windows 10 1809"
            '10.0 (17134)'          = "Windows 10 1803"
            '10.0 (16299)'          = "Windows 10 1709"
            '10.0 (15063)'          = "Windows 10 1703"
            '10.0 (14393)'          = "Windows 10 1607"
            '10.0 (10586)'          = "Windows 10 1511"
            '10.0 (10240)'          = "Windows 10 1507"
            '10.0.22000'            = 'Windows 11 21H2'
            '10.0.19043'            = 'Windows 10 21H1'
            '10.0.19042'            = 'Windows 10 20H2'
            '10.0.19041'            = 'Windows 10 2004'
            '10.0.18898'            = 'Windows 10 Insider Preview'
            '10.0.18363'            = "Windows 10 1909"
            '10.0.18362'            = "Windows 10 1903"
            '10.0.17763'            = "Windows 10 1809"
            '10.0.17134'            = "Windows 10 1803"
            '10.0.16299'            = "Windows 10 1709"
            '10.0.15063'            = "Windows 10 1703"
            '10.0.14393'            = "Windows 10 1607"
            '10.0.10586'            = "Windows 10 1511"
            '10.0.10240'            = "Windows 10 1507"
            '22000'                 = 'Windows 11 21H2'
            '19043'                 = 'Windows 10 21H1'
            '19042'                 = 'Windows 10 20H2'
            '19041'                 = 'Windows 10 2004'
            '18898'                 = 'Windows 10 Insider Preview'
            '18363'                 = "Windows 10 1909"
            '18362'                 = "Windows 10 1903"
            '17763'                 = "Windows 10 1809"
            '17134'                 = "Windows 10 1803"
            '16299'                 = "Windows 10 1709"
            '15063'                 = "Windows 10 1703"
            '14393'                 = "Windows 10 1607"
            '10586'                 = "Windows 10 1511"
            '10240'                 = "Windows 10 1507"
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } elseif ($OperatingSystem -like 'Windows Server*') {
        $Systems = @{'10.0 (20348)' = 'Windows Server 2022'
            '10.0 (19042)'          = 'Windows Server 2019 20H2'
            '10.0 (19041)'          = 'Windows Server 2019 2004'
            '10.0 (18363)'          = 'Windows Server 2019 1909'
            '10.0 (18362)'          = "Windows Server 2019 1903"
            '10.0 (17763)'          = "Windows Server 2019 1809"
            '10.0 (17134)'          = "Windows Server 2016 1803"
            '10.0 (14393)'          = "Windows Server 2016 1607"
            '6.3 (9600)'            = 'Windows Server 2012 R2'
            '6.1 (7601)'            = 'Windows Server 2008 R2'
            '5.2 (3790)'            = 'Windows Server 2003'
            '10.0.20348'            = 'Windows Server 2022'
            '10.0.19042'            = 'Windows Server 2019 20H2'
            '10.0.19041'            = 'Windows Server 2019 2004'
            '10.0.18363'            = 'Windows Server 2019 1909'
            '10.0.18362'            = "Windows Server 2019 1903"
            '10.0.17763'            = "Windows Server 2019 1809"
            '10.0.17134'            = "Windows Server 2016 1803"
            '10.0.14393'            = "Windows Server 2016 1607"
            '6.3.9600'              = 'Windows Server 2012 R2'
            '6.1.7601'              = 'Windows Server 2008 R2'
            '5.2.3790'              = 'Windows Server 2003'
            '20348'                 = 'Windows Server 2022'
            '19042'                 = 'Windows Server 2019 20H2'
            '19041'                 = 'Windows Server 2019 2004'
            '18363'                 = 'Windows Server 2019 1909'
            '18362'                 = "Windows Server 2019 1903"
            '17763'                 = "Windows Server 2019 1809"
            '17134'                 = "Windows Server 2016 1803"
            '14393'                 = "Windows Server 2016 1607"
            '9600'                  = 'Windows Server 2012 R2'
            '7601'                  = 'Windows Server 2008 R2'
            '3790'                  = 'Windows Server 2003'
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } else { $System = $OperatingSystem }
    if ($System) { $System } else { 'Unknown' }
}
function Copy-DictionaryManual { 
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Dictionary)
    $clone = @{}
    foreach ($Key in $Dictionary.Keys) {
        $value = $Dictionary.$Key
        $clonedValue = switch ($Dictionary.$Key) {
            { $null -eq $_ } {
                $null
                continue
            }
            { $_ -is [System.Collections.IDictionary] } {
                Copy-DictionaryManual -Dictionary $_
                continue
            }
            { $type = $_.GetType()
                $type.IsPrimitive -or $type.IsValueType -or $_ -is [string] } {
                $_
                continue
            }
            default { $_ | Select-Object -Property * }
        }
        if ($value -is [System.Collections.IList]) { $clone[$Key] = @($clonedValue) } else { $clone[$Key] = $clonedValue }
    }
    $clone
}
function Test-ComputerPort { 
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName,
        [int[]] $PortTCP,
        [int[]] $PortUDP,
        [int]$Timeout = 5000)
    begin {
        if ($Global:ProgressPreference -ne 'SilentlyContinue') {
            $TemporaryProgress = $Global:ProgressPreference
            $Global:ProgressPreference = 'SilentlyContinue'
        }
    }
    process {
        foreach ($Computer in $ComputerName) {
            foreach ($P in $PortTCP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'TCP'
                    'Status'                         = $null
                    'Summary'                        = $null
                    'Response'                       = $null
                }
                $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue
                if ($TcpClient.TcpTestSucceeded) {
                    $Output['Status'] = $TcpClient.TcpTestSucceeded
                    $Output['Summary'] = "TCP $P Successful"
                } else {
                    $Output['Status'] = $false
                    $Output['Summary'] = "TCP $P Failed"
                    $Output['Response'] = $Warnings
                }
                [PSCustomObject]$Output
            }
            foreach ($P in $PortUDP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'UDP'
                    'Status'                         = $null
                    'Summary'                        = $null
                }
                $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P)
                $UdpClient.Client.ReceiveTimeout = $Timeout
                $Encoding = [System.Text.ASCIIEncoding]::new()
                $byte = $Encoding.GetBytes("Evotec")
                [void]$UdpClient.Send($byte, $byte.length)
                $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0)
                try {
                    $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint)
                    [string]$Data = $Encoding.GetString($Bytes)
                    If ($Data) {
                        $Output['Status'] = $true
                        $Output['Summary'] = "UDP $P Successful"
                        $Output['Response'] = $Data
                    }
                } catch {
                    $Output['Status'] = $false
                    $Output['Summary'] = "UDP $P Failed"
                    $Output['Response'] = $_.Exception.Message
                }
                $UdpClient.Close()
                $UdpClient.Dispose()
                [PSCustomObject]$Output
            }
        }
    }
    end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } }
}
function Test-WinRM { 
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName)
    $Output = foreach ($Computer in $ComputerName) {
        $Test = [PSCustomObject] @{Output = $null
            Status                        = $null
            ComputerName                  = $Computer
        }
        try {
            $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop
            $Test.Status = $true
        } catch { $Test.Status = $false }
        $Test
    }
    $Output
}
function Add-ManagerInformation {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $SummaryDictionary,
        [string] $Type,
        [string] $ManagerType,
        [Object] $Key,
        [PSCustomObject] $User,
        [PSCustomObject] $Rule
        # [bool] $Enabled
    )
    #if ($Enabled) {
    if ($Key) {
        if ($Key -is [string]) {
            $KeyDN = $Key
        } else {
            $KeyDN = $Key.DisplayName
        }

        if (-not $SummaryDictionary[$KeyDN]) {
            $SummaryDictionary[$KeyDN] = [ordered] @{
                Manager             = $Key
                ManagerDefault      = [ordered] @{}
                ManagerNotCompliant = [ordered] @{}
                Security            = [ordered] @{}
            }
        }
        $SummaryDictionary[$KeyDN][$Type][$User.DistinguishedName] = [ordered] @{
            Manager       = $User.ManagerDN
            User          = $User
            Rule          = $Rule
            ManagerOption = $Type
            Output        = [ordered] @{}
        }
        $Default = [ordered] @{
            DisplayName     = $User.DisplayName
            Enabled         = $User.Enabled
            SamAccountName  = $User.SamAccountName
            Domain          = $User.Domain
            DateExpiry      = $User.DateExpiry
            DaysToExpire    = $User.DaysToExpire
            PasswordLastSet = $User.PasswordLastSet
            PasswordExpired = $User.PasswordExpired
        }
        if ($Type -ne 'ManagerDefault') {
            $Extended = [ordered] @{
                'Status'        = $ManagerType
                'Manager'       = $User.Manager
                'Manager Email' = $User.ManagerEmail
            }
            $SummaryDictionary[$KeyDN][$Type][$User.DistinguishedName]['Output'] = [PSCustomObject] ( $Extended + $Default)
        } else {
            $SummaryDictionary[$KeyDN][$Type][$User.DistinguishedName]['Output'] = [PSCustomObject] $Default
        }
    }
    # }
}
function Add-ParametersToString {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER String
    Parameter description
 
    .PARAMETER Parameter
    Parameter description
 
    .EXAMPLE
    $Test = 'this is a string $Test - and $Test2 AND $tEST3'
 
    Add-ParametersToString -String $Test -Parameter @{
        Testooo = 'sdsds'
        Test = 'oh my god'
        Test2 = 'ole ole'
        TEST3 = '56555'
    }
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $String,
        [System.Collections.IDictionary] $Parameter
    )
    $Sorted = $Parameter.Keys | Sort-Object { $_.length } -Descending


    foreach ($Key in $Sorted) {
        $String = $String -ireplace [Regex]::Escape("`$$Key"), $Parameter[$Key]
    }
    $String
}
function Get-GitHubVersion {
    [cmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $Cmdlet,
        [Parameter(Mandatory)][string] $RepositoryOwner,
        [Parameter(Mandatory)][string] $RepositoryName
    )
    $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue
    if ($App) {
        [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false)
        $LatestVersion = $GitHubReleases[0]
        if (-not $LatestVersion.Errors) {
            if ($App.Version -eq $LatestVersion.Version) {
                "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)"
            } elseif ($App.Version -lt $LatestVersion.Version) {
                "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?"
            } elseif ($App.Version -gt $LatestVersion.Version) {
                "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!"
            }
        } else {
            "Current: $($App.Version)"
        }
    }
}
function New-HTMLReport {
    [CmdletBinding()]
    param(
        $Report,
        $EmailParameters,
        $Logging,
        $FilePath,
        $SearchPath,
        $Rules,
        $UserSection,
        $ManagerSection,
        $SecuritySection,
        $AdminSection,
        $CachedUsers,
        $Summary,
        $SummaryUsersEmails,
        $SummaryManagersEmails,
        $SummaryEscalationEmails,
        $SummarySearch,
        $Locations,
        $AllSkipped
    )
    $TranslateOperators = @{
        'lt' = 'Less than'
        'gt' = 'Greater than'
        'eq' = 'Equal to'
        'ne' = 'Not equal to'
        'le' = 'Less than or equal to'
        'ge' = 'Greater than or equal to'
        'in' = 'In'
    }

    Write-Color -Text "[i]", " Generating HTML report ", $Report.Title -Color White, Yellow, Green
    if ($Report.DisableWarnings -eq $true) {
        $WarningAction = 'SilentlyContinue'
    } else {
        $WarningAction = 'Continue'
    }
    if (-not $Report.Title) {
        $Report.Title = "Password Solution Report"
    }

    # Create report
    New-HTML {
        New-HTMLHeader {
            New-HTMLSection -Invisible {
                New-HTMLSection {
                    New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue
                } -JustifyContent flex-start -Invisible
                New-HTMLSection {
                    New-HTMLText -Text "Password Solution - $($Script:Reporting['Version'])" -Color Blue
                } -JustifyContent flex-end -Invisible
            }
        }
        New-TableOption -DataStore JavaScript -ArrayJoin -BoolAsString
        if ($Report.ShowConfiguration) {
            New-HTMLTab -Name "About" {
                New-HTMLTab -Name "Configuration" {
                    New-HTMLSection -Invisible {
                        New-HTMLSection -HeaderText "Email Configuration" {
                            New-HTMLList {
                                foreach ($Key in $EmailParameters.Keys) {
                                    if ($Key -ne 'Password') {
                                        New-HTMLListItem -Text $Key, ": ", $EmailParameters[$Key] -FontWeight normal, normal, bold
                                    } else {
                                        New-HTMLListItem -Text $Key, ": ", "REDACTED" -FontWeight normal, normal, bold
                                    }
                                }
                            }
                        }
                        New-HTMLSection -HeaderText "Logging" {
                            New-HTMLList {
                                foreach ($Key in $Logging.Keys) {
                                    if ($Key -ne 'Password') {
                                        New-HTMLListItem -Text $Key, ": ", $Logging[$Key] -FontWeight normal, normal, bold
                                    } else {
                                        New-HTMLListItem -Text $Key, ": ", "REDACTED" -FontWeight normal, normal, bold
                                    }
                                }
                            }
                        }
                        <#
                                New-HTMLSection -HeaderText "Report Options" {
                                    New-HTMLList {
                                        foreach ($Key in $Report.Keys) {
                                            if ($Key -ne 'Password') {
                                                New-HTMLListItem -Text $Key, ": ", $HTMLReportPrimary[$Key] -FontWeight normal, normal, bold
                                            } else {
                                                New-HTMLListItem -Text $Key, ": ", "REDACTED" -FontWeight normal, normal, bold
                                            }
                                        }
                                    }
                                }
                                #>

                        New-HTMLSection -HeaderText "Other" {
                            New-HTMLList {
                                New-HTMLListItem -Text 'FilePath', ": ", $FilePath -FontWeight normal, normal, bold
                                New-HTMLListItem -Text 'SearchPath', ": ", $SearchPath -FontWeight normal, normal, bold
                            }
                        }
                    }

                    New-HTMLSection -Invisible {
                        New-HTMLSection -HeaderText "User Section" {
                            New-HTMLList {
                                New-HTMLListItem -Text "Enabled: ", $UserSection.Enable -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "SendCountMaximum: ", $UserSection.SendCountMaximum -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "SendToDefaultEmail: ", $UserSection.SendToDefaultEmail -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "DefaultEmail: ", ($UserSection.DefaultEmail -join ", ") -FontWeight normal, bold -TextDecoration underline, none
                            }
                        }
                        New-HTMLSection -HeaderText "Manager Section" {
                            New-HTMLList {
                                New-HTMLListItem -Text "Enabled: ", $ManagerSection.Enable -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "SendCountMaximum: ", $ManagerSection.SendCountMaximum -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "DefaultEmail: ", ($ManagerSection.DefaultEmail -join ", ") -FontWeight normal, bold -TextDecoration underline, none
                            }
                        }
                        New-HTMLSection -HeaderText "Security Section" {
                            New-HTMLList {
                                New-HTMLListItem -Text "Enabled: ", $SecuritySection.Enable -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "SendCountMaximum: ", $SecuritySection.SendCountMaximum -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "SendToDefaultEmail: ", $SecuritySection.SendToDefaultEmail -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "DefaultEmail: ", ($SecuritySection.DefaultEmail -join ", ") -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "Attach CSV: ", ($SecuritySection.AttachCSV -join ",") -FontWeight normal, bold -TextDecoration underline, none
                            }
                        }
                        New-HTMLSection -HeaderText "Admin Section" {
                            New-HTMLList {
                                New-HTMLListItem -Text "Enabled: ", $AdminSection.Enable -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "Subject: ", $AdminSection.Subject -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "Manager: ", $AdminSection.Manager.DisplayName -FontWeight normal, bold -TextDecoration underline, none
                                New-HTMLListItem -Text "Manager Email: ", ($AdminSection.Manager.EmailAddress -join ", ") -FontWeight normal, bold -TextDecoration underline, none
                            }
                        }
                    }
                }
                New-HTMLTab -Name 'Rules Configuration' {
                    New-HTMLText -Text "There are ", $Rules.Count, " rules defined in the Password Solution. ", "Please keep in mind that order of the rules matter." -FontWeight normal, bold, normal -Color None, Blue, None

                    foreach ($Rule in $Rules) {
                        if ($Rule.Enable) {
                            $SectionColor = 'SpringGreen'
                        } else {
                            $SectionColor = 'Coral'
                        }
                        New-HTMLSection -HeaderText "Rule $($Rule.Name)" -CanCollapse -HeaderBackGroundColor $SectionColor {
                            New-HTMLList {
                                if ($Rule.Enable) {
                                    New-HTMLListItem -Text "Rule ", $Rule.Name, " is ", "enabled" -FontWeight normal, bold, normal, bold, normal, normal -Color None, None, None, Green
                                } else {
                                    New-HTMLListItem -Text "Rule ", $Rule.Name, " is ", "disabled" -FontWeight normal, bold, normal, bold, normal, normal -Color None, None, None, Red
                                }
                                New-HTMLList {
                                    New-HTMLListItem -Text "Notify till expiry on ", $($Rule.Reminders -join ","), " day " -FontWeight normal, bold, normal
                                    if ($Rule.IncludeExpiring) {
                                        New-HTMLListItem -Text "Include expiring accounts is ", "enabled" -FontWeight bold, bold -Color None, Green
                                    } else {
                                        New-HTMLListItem -Text "Include expiring accounts is ", "disabled" -FontWeight bold, bold -Color None, Red
                                    }
                                    if ($Rule.IncludePasswordNeverExpires) {
                                        New-HTMLListItem -Text "Include passwords never expiring with ", $Rule.PasswordNeverExpiresDays, " days rule" -FontWeight bold -Color Amethyst
                                    } else {
                                        New-HTMLListItem -Text "Do not include passwords that never expire." -FontWeight bold -Color Blue
                                    }
                                    if ($Rule.IncludeName.Count -gt 0 -and $Rule.IncludeNameProperties.Count -gt 0) {
                                        New-HTMLListItem -Text "Apply naming rule to require that account contains of of names ", $($Rule.IncludeName -join ", "), " in at least one property ", ($Rule.IncludeNameProperties -join ", ") -FontWeight normal, bold, normal, bold, normal -Color None, Blue, None, Blue
                                    } else {
                                        New-HTMLListItem -Text "Do not apply special name rules" -Color Blue -FontWeight bold
                                    }
                                    if ($Rule.IncludeOU) {
                                        New-HTMLListItem -Text "Apply Organizational Unit inclusion on ", ($Rule.IncludeOU -join ", ") -FontWeight normal, bold -Color None, Blue
                                    } else {
                                        New-HTMLListItem -Text "Do not apply Organizational Unit limit" -Color Blue -FontWeight bold
                                    }
                                    if ($Rule.ExcludeOU) {
                                        New-HTMLListItem -Text "Apply Organizational Unit exclusion on ", $Rule.ExcludeOU -FontWeight normal, bold -Color None, Green
                                    } else {
                                        New-HTMLListItem -Text "Do not exclude any Organizational Unit" -Color Blue -FontWeight bold
                                    }
                                    if ($Rule.IncludeGroup) {
                                        New-HTMLListItem -Text "Appply Group Membership inclusion (direct only) ", ($Rule.IncludeGroup -join ", ")
                                    } else {
                                        New-HTMLListItem -Text "Do not apply Group Membership limit"
                                    }
                                    if ($Rule.ExcludeGroup) {
                                        New-HTMLListItem -Text "Apply Group Membership exclusion (direct only): ", ($Rule.ExcludeGroup -join ", ")
                                    } else {
                                        New-HTMLListItem -Text "Do not apply Group Membership exclusion"
                                    }
                                    New-HTMLListItem -Text "Send to manager" -NestedListItems {
                                        New-HTMLList {
                                            if ($Rule.SendToManager.Manager.Enable) {
                                                New-HTMLListItem -Text "Manager ", " is ", 'enabled' -FontWeight bold, normal, bold -Color None, None, Green {
                                                    New-HTMLList {
                                                        New-HTMLListItem -Text "Rules: " {
                                                            New-HTMLList {
                                                                if ($Rule.SendToManager.Manager.Reminders.Default.Enable) {
                                                                    if ($Rule.SendToManager.Manager.Reminders.Default.Reminder) {
                                                                        New-HTMLListItem -Text "Default ", "is enabled", " sent on ", $($Rule.SendToManager.Manager.Reminders.Default.Reminder -join ", "), " days to expiry of user." -FontWeight normal, bold, normal, bold, normal -Color None, Green, None, Green
                                                                    } else {
                                                                        New-HTMLListItem -Text "Default ", "is enabled", " sent on ", $($Rule.Reminders -join ", "), " days to expiry of user." -FontWeight normal, bold, normal, bold, normal -Color None, Green, None, Green
                                                                    }
                                                                } else {
                                                                    New-HTMLListItem -Text "Default rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                                if ($Rule.SendToManager.Manager.Reminders.OnDay.Enable) {
                                                                    New-HTMLListItem -Text @(
                                                                        "On day of the week ", "is ", "enabled"
                                                                        " on days: ", ($Rule.SendToManager.Manager.Reminders.OnDay.Days -join ", "),
                                                                        " with comparison ", $TranslateOperators[$Rule.SendToManager.Manager.Reminders.OnDay.ComparisonType],
                                                                        ' value ', $Rule.SendToManager.Manager.Reminders.OnDay.Reminder
                                                                    ) -FontWeight bold, normal, bold, normal, bold, normal, bold, normal, bold -Color None, None, Green, None, Green, None, Green, None, Green
                                                                } else {
                                                                    New-HTMLListItem -Text "On day of week rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                                if ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Enable) {
                                                                    New-HTMLListItem -Text @(
                                                                        "On day of the month rule ", "is", " enabled",
                                                                        " on days ", ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Days -join ","),
                                                                        " with comparison ", $TranslateOperators[$Rule.SendToManager.Manager.Reminders.OnDayOfMonth.ComparisonType],
                                                                        ' value ', $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder
                                                                    ) -FontWeight bold, normal, bold, normal, bold, normal, bold, normal, bold -Color None, None, Green, None, Green, None, Green, None, Green
                                                                } else {
                                                                    New-HTMLListItem -Text "On day of month rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            } else {
                                                New-HTMLListItem -Text "Manager ", " is ", 'disabled' -FontWeight bold, normal, bold -Color None, None, Red
                                            }
                                            if ($Rule.SendToManager.ManagerNotCompliant.Enable) {
                                                New-HTMLListItem -Text "Manager Escalation", " is ", 'enabled' -FontWeight bold, normal, bold -Color None, None, Green {
                                                    New-HTMLList {
                                                        New-HTMLListItem -Text "Manager Name: ", $Rule.SendToManager.ManagerNotCompliant.Manager.DisplayName -FontWeight normal, bold -TextDecoration underline, none
                                                        New-HTMLListItem -Text "Manager Email Address: ", $Rule.SendToManager.ManagerNotCompliant.Manager.EmailAddress -FontWeight normal, bold -TextDecoration underline, none
                                                    }
                                                    New-HTMLList {
                                                        New-HTMLListItem -Text "Rules: " {
                                                            New-HTMLList {
                                                                if ($Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Enable) {
                                                                    if ($Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder) {
                                                                        New-HTMLListItem -Text "Default ", "is enabled", " sent on ", $($Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder -join ", "), " days to expiry of user." -FontWeight normal, bold, normal, bold, normal -Color None, Green, None, Green
                                                                    } else {
                                                                        New-HTMLListItem -Text "Default ", "is enabled", " sent on ", $($Rule.Reminders -join ", "), " days to expiry of user." -FontWeight normal, bold, normal, bold, normal -Color None, Green, None, Green
                                                                    }
                                                                } else {
                                                                    New-HTMLListItem -Text "Default rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                                if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Enable) {
                                                                    New-HTMLListItem -Text @(
                                                                        "On day of the week ", "is ", "enabled"
                                                                        " on days: ", ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Days -join ", "),
                                                                        " with comparison ", $TranslateOperators[$Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType],
                                                                        ' value ', $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder
                                                                    ) -FontWeight bold, normal, bold, normal, bold, normal, bold, normal, bold -Color None, None, Green, None, Green, None, Green, None, Green
                                                                } else {
                                                                    New-HTMLListItem -Text "On day of week rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                                if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Enable) {
                                                                    New-HTMLListItem -Text @(
                                                                        "On day of the month rule ", "is", " enabled",
                                                                        " on days ", ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Days -join ", "),
                                                                        " with comparison ", $TranslateOperators[$Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType],
                                                                        ' value ', $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder
                                                                    ) -FontWeight bold, normal, bold, normal, bold, normal, bold, normal, bold -Color None, None, Green, None, Green, None, Green, None, Green
                                                                } else {
                                                                    New-HTMLListItem -Text "On day of month rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            } else {
                                                New-HTMLListItem -Text "Manager Escalation", " is ", "disabled" -FontWeight bold, normal, bold -Color None, None, Red
                                            }
                                            if ($Rule.SendToManager.SecurityEscalation.Enable) {
                                                New-HTMLListItem -Text "Security Escalation ", "is", " enabled" -FontWeight bold, normal, bold -Color None, None, Green {
                                                    New-HTMLList {
                                                        New-HTMLListItem -Text "Manager Name: ", $Rule.SendToManager.SecurityEscalation.Manager.DisplayName -FontWeight normal, bold -TextDecoration underline, none
                                                        New-HTMLListItem -Text "Manager Email Address: ", $Rule.SendToManager.SecurityEscalation.Manager.EmailAddress -FontWeight normal, bold -TextDecoration underline, none
                                                    }
                                                    New-HTMLList {
                                                        New-HTMLListItem -Text "Rules: " {
                                                            New-HTMLList {
                                                                <#
                                                                        if ($Rule.SendToManager.SecurityEscalation.Reminders.Default.Enable) {
                                                                            New-HTMLListItem -Text "Default: ", $Rule.SendToManager.SecurityEscalation.Reminders.Default.Enable
                                                                        } else {
                                                                            New-HTMLListItem -Text "Default rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                        }
                                                                        #>

                                                                if ($Rule.SendToManager.SecurityEscalation.Reminders.Default.Enable) {
                                                                    if ($Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder) {
                                                                        New-HTMLListItem -Text "Default ", "is enabled", " sent on ", $($Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder -join ", "), " days to expiry of user." -FontWeight normal, bold, normal, bold, normal -Color None, Green, None, Green
                                                                    } else {
                                                                        New-HTMLListItem -Text "Default ", "is enabled", " sent on ", $($Rule.Reminders -join ", "), " days to expiry of user." -FontWeight normal, bold, normal, bold, normal -Color None, Green, None, Green
                                                                    }
                                                                } else {
                                                                    New-HTMLListItem -Text "Default rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                                if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Enable) {
                                                                    New-HTMLListItem -Text @(
                                                                        "On day of the week ", "is ", "enabled"
                                                                        " on days: ", ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Days -join ", "),
                                                                        " with comparison ", $TranslateOperators[$Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType],
                                                                        ' value ', $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder
                                                                    ) -FontWeight bold, normal, bold, normal, bold, normal, bold, normal, bold -Color None, None, Green, None, Green, None, Green, None, Green
                                                                } else {
                                                                    New-HTMLListItem -Text "On day of week rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                                if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Enable) {
                                                                    New-HTMLListItem -Text @(
                                                                        "On day of the month rule ", "is", " enabled",
                                                                        " on days ", ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Days -join ", "),
                                                                        " with comparison ", $TranslateOperators[$Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType],
                                                                        ' value ', $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder
                                                                    ) -FontWeight bold, normal, bold, normal, bold, normal, bold, normal, bold -Color None, None, Green, None, Green, None, Green, None, Green
                                                                } else {
                                                                    New-HTMLListItem -Text "On day of month rule is ", "disabled" -FontWeight bold, bold -Color None, Red
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            } else {
                                                New-HTMLListItem -Text "Security Escalation", " is ", "disabled" -FontWeight bold, normal, bold -Color None, None, Red
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        if ($Report.ShowAllUsers) {
            New-HTMLTab -Name 'All Users' {
                New-HTMLTable -DataTable $CachedUsers.Values -Filtering {
                    New-TableCondition -Name 'Enabled' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq
                    New-TableCondition -Name 'HasMailbox' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq
                    New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string
                    New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                    New-TableCondition -Name 'PasswordAtNextLogon' -BackgroundColor BlueSmoke -FailBackgroundColor LawnGreen -Value $true -ComparisonType string
                    New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Missing', 'Disabled' -BackgroundColor Salmon -Operator in
                    New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Enabled' -BackgroundColor LawnGreen
                    New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Not available' -BackgroundColor BlueSmoke
                }
            }
        }
        if ($Report.ShowRules) {
            foreach ($Rule in  $Summary['Rules'].Keys) {
                if ((Measure-Object -InputObject $Summary['Rules'][$Rule].Values.User).Count -gt 0) {
                    $Color = 'LawnGreen'
                    $IconSolid = 'Star'
                } else {
                    $Color = 'Salmon'
                    $IconSolid = 'Stop'
                }
                New-HTMLTab -Name $Rule -TextColor $Color -IconColor $Color -IconSolid $IconSolid {
                    New-HTMLTable -DataTable $Summary['Rules'][$Rule].Values.User -Filtering {
                        New-TableCondition -Name 'Enabled' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string
                        New-TableCondition -Name 'HasMailbox' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq
                        New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                        New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                        New-TableCondition -Name 'PasswordAtNextLogon' -BackgroundColor BlueSmoke -FailBackgroundColor LawnGreen -Value $true -ComparisonType string
                        New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Missing', 'Disabled' -BackgroundColor Salmon -Operator in
                        New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Enabled' -BackgroundColor LawnGreen
                        New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Not available' -BackgroundColor BlueSmoke
                    }
                }
            }
        }
        if ($Report.ShowUsersSent) {
            if ((Measure-Object -InputObject $SummaryUsersEmails).Count -gt 0) {
                $Color = 'BrightTurquoise'
                $IconSolid = 'sticky-note'
            } else {
                $Color = 'Amaranth'
                $IconSolid = 'stop-circle'
            }
            New-HTMLTab -Name 'Email sent to users' -TextColor $Color -IconColor $Color -IconSolid $IconSolid {
                New-HTMLTable -DataTable $SummaryUsersEmails {
                    New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary'
                    New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo'
                    New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                    New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                } -Filtering
            }
        }
        if ($Report.ShowManagersSent) {
            if ((Measure-Object -InputObject $SummaryManagersEmails).Count -gt 0) {
                $Color = 'BrightTurquoise'
                $IconSolid = 'sticky-note'
            } else {
                $Color = 'Amaranth'
                $IconSolid = 'stop-circle'
            }
            New-HTMLTab -Name 'Email sent to manager' -TextColor $Color -IconColor $Color -IconSolid $IconSolid {
                New-HTMLTable -DataTable $SummaryManagersEmails {
                    New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary'
                    New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo'
                } -Filtering
            }
        }
        if ($Report.ShowEscalationSent) {
            if ((Measure-Object -InputObject $SummaryEscalationEmails).Count -gt 0) {
                $Color = 'BrightTurquoise'
                $IconSolid = 'sticky-note'
            } else {
                $Color = 'Amaranth'
                $IconSolid = 'stop-circle'
            }
            New-HTMLTab -Name 'Email sent to Security' -TextColor $Color -IconColor $Color -IconSolid $IconSolid {
                New-HTMLTable -DataTable $SummaryEscalationEmails {
                    New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary'
                    New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo'
                } -Filtering
            }
        }
        if ($Report.ShowSearchUsers) {
            [Array] $UsersSent = $SummarySearch['EmailSent'].Values #| ForEach-Object { if ($_ -ne $null) { $_ } }
            if ($UsersSent.Count -gt 0) {
                $Color = 'BrightTurquoise'
                $IconSolid = 'sticky-note'
            } else {
                $Color = 'Amaranth'
                $IconSolid = 'stop-circle'
            }
            New-HTMLTab -Name 'Users notified' -TextColor $Color -IconColor $Color -IconSolid $IconSolid {
                New-HTMLTable -DataTable $UsersSent {
                    New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary'
                    New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo'
                    New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                    New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                } -Filtering
            }
        }
        if ($Report.ShowSearchManagers) {
            [Array] $ShowSearchManagers = $SummarySearch['EmailManagers'].Values #| ForEach-Object { if ($_ -ne $null) { $_ } }
            if ($ShowSearchManagers.Count -gt 0) {
                $Color = 'BrightTurquoise'
                $IconSolid = 'sticky-note'
            } else {
                $Color = 'Amaranth'
                $IconSolid = 'stop-circle'
            }
            New-HTMLTab -Name 'Email sent to manager' -TextColor $Color -IconColor $Color -IconSolid $IconSolid {
                New-HTMLTable -DataTable $ShowSearchManagers {
                    New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary'
                    New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo'
                } -Filtering
            }
        }
        if ($Report.ShowSearchEscalations) {
            [Array] $ShowSearchEscalations = $SummarySearch['EmailEscalations'].Values #| ForEach-Object { if ($_ -ne $null) { $_ } }
            if ($ShowSearchEscalations.Count -gt 0) {
                $Color = 'BrightTurquoise'
                $IconSolid = 'sticky-note'
            } else {
                $Color = 'Amaranth'
                $IconSolid = 'stop-circle'
            }
            New-HTMLTab -Name 'Email sent to Security' -TextColor $Color -IconColor $Color -IconSolid $IconSolid {
                New-HTMLTable -DataTable $ShowSearchEscalations {
                    New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary'
                    New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo'
                } -Filtering
            }
        }
        if ($Report.ShowSkippedUsers) {
            New-HTMLTab -Name 'Skipped Users' -IconSolid users {
                New-HTMLPanel -AlignContentText center {
                    New-HTMLText -FontSize 15pt -Text "Those users have no password date set. This means account running expiration checks doesn't have permissions or acccout never had password set or account is set to change password on logon. "
                } -Invisible
                New-HTMLTable -DataTable $AllSkipped.Values -Filtering {
                    New-TableCondition -Name 'Enabled' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq
                    New-TableCondition -Name 'HasMailbox' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq
                    New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string
                    New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string
                    New-TableCondition -Name 'PasswordAtNextLogon' -BackgroundColor BlueSmoke -FailBackgroundColor LawnGreen -Value $true -ComparisonType string
                    New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Missing', 'Disabled' -BackgroundColor Salmon -Operator in
                    New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Enabled' -BackgroundColor LawnGreen
                    New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Not available' -BackgroundColor BlueSmoke
                }
            }
        }
        if ($Report.ShowSkippedLocations) {
            New-HTMLTab -Name 'Skipped Locations' -IconSolid building {
                New-HTMLTable -DataTable $Locations.Values -Filtering {
                    New-TableHeader -ResponsiveOperations none -Names 'Names', 'NamesExpired'
                }
            }
        }
    } -ShowHTML:$Report.ShowHTML -FilePath $Report.FilePath -Online:$Report.Online -WarningAction $WarningAction -TitleText $Report.Title

    Write-Color -Text "[i]" , " Generating HTML report ", $Report.Title, ". Done" -Color White, Yellow, Green
}
function Send-PasswordEmail {
    [CmdletBinding()]
    param(
        [scriptblock] $Template,
        [PSCustomObject] $User,
        [Array] $ManagedUsers,
        [Array] $ManagedUsersManagerNotCompliant,
        [Array] $SummaryUsersEmails,
        [Array] $SummaryManagersEmails,
        [Array] $SummaryEscalationEmails,
        [string] $TimeToProcess,
        [Array] $Attachments,
        [System.Collections.IDictionary] $EmailParameters,
        [string] $Subject
    )

    if ($Template) {
        $SourceParameters = [ordered] @{
            ManagerDisplayName                   = $User.DisplayName
            ManagerUsersTable                    = $ManagedUsers
            ManagerUsersTableManagerNotCompliant = $ManagedUsersManagerNotCompliant
            SummaryEscalationEmails              = $SummaryEscalationEmails
            SummaryManagersEmails                = $SummaryManagersEmails
            SummaryUsersEmails                   = $SummaryUsersEmails
            TimeToProcess                        = $TimeToProcess
            # Only works if User is set
            UserPrincipalName                    = $User.UserPrincipalName     # : adm.pklys@ad.evotec.xyz
            SamAccountName                       = $User.SamAccountName        # : adm.pklys
            Domain                               = $User.Domain                # : ad.evotec.xyz
            Enabled                              = $User.Enabled
            EmailAddress                         = $User.EmailAddress          # :
            DateExpiry                           = $User.DateExpiry            # :
            DaysToExpire                         = $User.DaysToExpire          # :
            PasswordExpired                      = $User.PasswordExpired       # : False
            PasswordLastSet                      = $User.PasswordLastSet       # : 05.09.2020 11:07:29
            PasswordNotRequired                  = $User.PasswordNotRequired   # : False
            PasswordNeverExpires                 = $User.PasswordNeverExpires  # : True
            ManagerSamAccountName                = $User.ManagerSamAccountName # : przemyslaw.klys
            ManagerEmail                         = $User.ManagerEmail          # : przemyslaw.klys@evotec.pl
            ManagerStatus                        = $User.ManagerStatus         # : Enabled
            ManagerLastLogonDays                 = $User.ManagerLastLogonDays  # : 0
            Manager                              = $User.Manager               # : PrzemysÅ‚aw KÅ‚ys
            DisplayName                          = $User.DisplayName           # : Administrator PrzemysÅ‚aw KÅ‚ys
            GivenName                            = $User.GivenName             # : Administrator PrzemysÅ‚aw
            Surname                              = $User.Surname               # : KÅ‚ys
            OrganizationalUnit                   = $User.OrganizationalUnit    # : OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz
            MemberOf                             = $User.MemberOf              # : {CN=GDS-TestGroup4,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz, CN=GDS-TestGroup2,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz, CN=Domain Admins,CN=Users,DC=ad,DC=evotec,DC=xyz}
            DistinguishedName                    = $User.DistinguishedName     # : CN=Administrator PrzemysÅ‚aw KÅ‚ys,OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz
            ManagerDN                            = $User.ManagerDN             # : CN=PrzemysÅ‚aw KÅ‚ys,OU=Users,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz
        }
        $Body = EmailBody -EmailBody $Template -Parameter $SourceParameters

        # Below command would require to define variables as they are used in scriptblock
        #$EmailParameters.Subject = $ExecutionContext.InvokeCommand.ExpandString($Subject)
        # following replacement is a bit more cumbersome the the one above but a bit more secure and doesn't require creating 20+ unused variables
        $EmailParameters.Subject = Add-ParametersToString -String $Subject -Parameter $SourceParameters
        $EmailParameters.Body = $Body
        if ($Attachments) {
            $EmailParameters.Attachment = $Attachments
        } else {
            $EmailParameters.Attachment = @()
        }
        Send-EmailMessage @EmailParameters
    }
}
function Find-Password {
    <#
    .SYNOPSIS
    Scan Active Directory forest for all users and their password expiration date
 
    .DESCRIPTION
    Scan Active Directory forest for all users and their password expiration date
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER OverwriteEmailProperty
    Overwrite EmailAddress property with different property name
 
    .PARAMETER AsHashTable
    Return result as HashTable, instead of Array
 
    .EXAMPLE
    Find-Password | ft
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string] $OverwriteEmailProperty,
        [switch] $AsHashTable
    )
    $Today = Get-Date

    $Properties = @(
        'Manager', 'DisplayName', 'GivenName', 'Surname', 'SamAccountName', 'EmailAddress', 'msDS-UserPasswordExpiryTimeComputed', 'PasswordExpired', 'PasswordLastSet', 'PasswordNotRequired', 'Enabled', 'PasswordNeverExpires', 'Mail', 'MemberOf', 'LastLogonDate', 'Name'
        'userAccountControl'
        'msExchMailboxGuid'
        'pwdLastSet', 'ObjectClass'
        if ($OverwriteEmailProperty) {
            $OverwriteEmailProperty
        }
    )
    $PropertiesContacts = @(
        'SamAccountName', 'CanonicalName', 'WhenChanged', 'WhenChanged', 'DisplayName', 'DistinguishedName', 'Name', 'Mail', 'TargetAddress', 'ObjectClass'
    )

    # We're caching all users to make sure it's speedy gonzales when querying for Managers
    if (-not $CachedUsers) {
        $CachedUsers = [ordered] @{ }
    }
    if (-not $Cache) {
        $Cache = [ordered] @{ }
    }
    Write-Color -Text "[i] Discovering forest information" -Color White, Yellow, White, Yellow, White, Yellow, White
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExcludeDomains $ExcludeDomains -IncludeDomains $IncludeDomains -ExtendedForestInformation $ExtendedForestInformation

    [Array] $Users = foreach ($Domain in $ForestInformation.Domains) {
        Write-Color -Text "[i] Discovering DC for domain ", "$($Domain)", " in forest ", $ForestInformation.Name -Color White, Yellow, White, Yellow, White, Yellow, White
        $Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]

        Write-Color -Text "[i] Getting users from ", "$($Domain)", " using ", $Server -Color White, Yellow, White, Yellow, White, Yellow, White
        try {
            Get-ADUser -Server $Server -Filter '*' -Properties $Properties -ErrorAction Stop
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            Write-Color '[e] Error: ', $ErrorMessage -Color White, Red
        }
    }
    [Array] $Contacts = foreach ($Domain in $ForestInformation.Domains) {
        Write-Color -Text "[i] Discovering DC for domain ", "$($Domain)", " in forest ", $ForestInformation.Name -Color White, Yellow, White, Yellow, White, Yellow, White
        $Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]

        Write-Color -Text "[i] Getting contacts from ", "$($Domain)", " using ", $Server -Color White, Yellow, White, Yellow, White, Yellow, White
        try {
            Get-ADObject -LDAPFilter "objectClass=Contact" -Server $Server -Properties $PropertiesContacts -ErrorAction Stop
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            Write-Color '[e] Error: ', $ErrorMessage -Color White, Red
        }
    }
    foreach ($User in $Users) {
        $Cache[$User.DistinguishedName] = $User
    }
    foreach ($Contact in $Contacts) {
        $Cache[$Contact.DistinguishedName] = $Contact
    }
    Write-Color -Text "[i] Preparing all users for password expirations in forest ", $Forest.Name -Color White, Yellow, White, Yellow, White, Yellow, White
    foreach ($User in $Users) {
        $DateExpiry = $null
        $DaysToExpire = $null
        $PasswordDays = $null
        $PasswordNeverExpires = $null
        $PasswordAtNextLogon = $null
        $HasMailbox = $null
        #$UserManager = $Cache["$($User.Manager)"]
        if ($User.Manager) {
            $Manager = $Cache[$User.Manager].DisplayName
            $ManagerSamAccountName = $Cache[$User.Manager].SamAccountName
            $ManagerDisplayName = $Cache[$User.Manager].DisplayName
            $ManagerEmail = $Cache[$User.Manager].Mail
            $ManagerEnabled = $Cache[$User.Manager].Enabled
            $ManagerLastLogon = $Cache[$User.Manager].LastLogonDate
            if ($ManagerLastLogon) {
                $ManagerLastLogonDays = $( - $($ManagerLastLogon - $Today).Days)
            } else {
                $ManagerLastLogonDays = $null
            }
            $ManagerType = $Cache[$User.Manager].ObjectClass
        } else {
            if ($User.ObjectClass -eq 'user') {
                $ManagerStatus = 'Missing'
            } else {
                $ManagerStatus = 'Not available'
            }
            $Manager = $null
            $ManagerSamAccountName = $null
            $ManagerDisplayName = $null
            $ManagerEmail = $null
            $ManagerEnabled = $null
            $ManagerLastLogon = $null
            $ManagerLastLogonDays = $null
            $ManagerType = $null
        }

        if ($OverwriteEmailProperty) {
            # fix this for a user
            $EmailTemp = $User.$OverwriteEmailProperty
            if ($EmailTemp -like '*@*') {
                $EmailAddress = $EmailTemp
            } else {
                $EmailAddress = $User.EmailAddress
            }
            # Fix this for manager as well
            if ($Cache["$($User.Manager)"]) {
                if ($Cache["$($User.Manager)"].$OverwriteEmailProperty -like '*@*') {
                    # $UserManager.Mail = $UserManager.$OverwriteEmailProperty
                    $ManagerEmail = $Cache["$($User.Manager)"].$OverwriteEmailProperty
                }
            }
        } else {
            $EmailAddress = $User.EmailAddress
        }

        if ($User.PasswordLastSet) {
            $PasswordDays = (New-TimeSpan -Start ($User.PasswordLastSet) -End ($Today)).Days
        } else {
            $PasswordDays = $null
        }

        # Since we fixed manager above, we now check for status
        if ($User.Manager) {
            if ($ManagerEnabled -and $ManagerEmail) {
                if ((Test-EmailAddress -EmailAddress $ManagerEmail).IsValid -eq $true) {
                    $ManagerStatus = 'Enabled'
                } else {
                    $ManagerStatus = 'Enabled, bad email'
                }
            } elseif ($ManagerEnabled) {
                $ManagerStatus = 'No email'
            } elseif ($Cache[$User.Manager].ObjectClass -eq 'Contact') {
                $ManagerStatus = 'Enabled' # we need to treat it as always enabled
            } else {
                $ManagerStatus = 'Disabled'
            }
        }

        if ($User."msDS-UserPasswordExpiryTimeComputed" -ne 9223372036854775807) {
            # This is standard situation where users password is expiring as needed
            try {
                $DateExpiry = ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed"))
            } catch {
                $DateExpiry = $User."msDS-UserPasswordExpiryTimeComputed"
            }
            try {
                $DaysToExpire = (New-TimeSpan -Start ($Today) -End ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed"))).Days
            } catch {
                $DaysToExpire = $null
            }
            $PasswordNeverExpires = $User.PasswordNeverExpires
        } else {
            # This is non-standard situation. This basically means most likely Fine Grained Group Policy is in action where it makes PasswordNeverExpires $true
            # Since FGP policies are a bit special they do not tick the PasswordNeverExpires box, but at the same time value for "msDS-UserPasswordExpiryTimeComputed" is set to 9223372036854775807
            $PasswordNeverExpires = $true
        }

        if ($User.pwdLastSet -eq 0 -and $DateExpiry.Year -eq 1601) {
            $PasswordAtNextLogon = $true
        } else {
            $PasswordAtNextLogon = $false
        }

        if ($PasswordNeverExpires -or $null -eq $User.PasswordLastSet) {
            # If password last set is null or password never expires is set to true, then date of expiry and days to expire is not applicable
            $DateExpiry = $null
            $DaysToExpire = $null
        }

        $UserAccountControl = Convert-UserAccountControl -UserAccountControl $User.UserAccountControl
        if ($UserAccountControl -contains 'INTERDOMAIN_TRUST_ACCOUNT') {
            continue
        }
        if ($User.'msExchMailboxGuid') {
            $HasMailbox = $true
        } else {
            $HasMailbox = $false
        }

        $MyUser = [ordered] @{
            UserPrincipalName     = $User.UserPrincipalName
            SamAccountName        = $User.SamAccountName
            Domain                = ConvertFrom-DistinguishedName -DistinguishedName $User.DistinguishedName -ToDomainCN
            RuleName              = ''
            RuleOptions           = [System.Collections.Generic.List[string]]::new()
            Enabled               = $User.Enabled
            HasMailbox            = $HasMailbox
            EmailAddress          = $EmailAddress
            DateExpiry            = $DateExpiry
            DaysToExpire          = $DaysToExpire
            PasswordExpired       = $User.PasswordExpired
            PasswordDays          = $PasswordDays
            PasswordAtNextLogon   = $PasswordAtNextLogon
            PasswordLastSet       = $User.PasswordLastSet
            PasswordNotRequired   = $User.PasswordNotRequired
            PasswordNeverExpires  = $PasswordNeverExpires
            Manager               = $Manager
            ManagerDisplayName    = $ManagerDisplayName
            ManagerSamAccountName = $ManagerSamAccountName
            ManagerEmail          = $ManagerEmail
            ManagerStatus         = $ManagerStatus
            ManagerLastLogonDays  = $ManagerLastLogonDays
            ManagerType           = $ManagerType
            DisplayName           = $User.DisplayName
            Name                  = $User.Name
            GivenName             = $User.GivenName
            Surname               = $User.Surname
            OrganizationalUnit    = ConvertFrom-DistinguishedName -DistinguishedName $User.DistinguishedName -ToOrganizationalUnit
            MemberOf              = $User.MemberOf
            DistinguishedName     = $User.DistinguishedName
            ManagerDN             = $User.Manager
        }
        foreach ($Property in $ConditionProperties) {
            $MyUser["$Property"] = $User.$Property
        }
        $CachedUsers["$($User.DistinguishedName)"] = [PSCustomObject] $MyUser
    }
    foreach ($Contact in $Contacts) {
        # create dummy objects for manager contacts
        $MyUser = [ordered] @{
            UserPrincipalName     = $null
            SamAccountName        = $null
            Domain                = ConvertFrom-DistinguishedName -DistinguishedName $Contact.DistinguishedName -ToDomainCN
            RuleName              = ''
            RuleOptions           = [System.Collections.Generic.List[string]]::new()
            Enabled               = $true
            HasMailbox            = $null
            EmailAddress          = $Contact.Mail
            DateExpiry            = $null
            DaysToExpire          = $null
            PasswordExpired       = $null
            PasswordDays          = $null
            PasswordAtNextLogon   = $null
            PasswordLastSet       = $null
            PasswordNotRequired   = $null
            PasswordNeverExpires  = $null
            Manager               = $null
            ManagerDisplayName    = $null
            ManagerSamAccountName = $null
            ManagerEmail          = $null
            ManagerStatus         = $null
            ManagerLastLogonDays  = $null
            ManagerType           = $null
            DisplayName           = $Contact.DisplayName
            Name                  = $Contact.Name
            GivenName             = $null
            Surname               = $null
            OrganizationalUnit    = ConvertFrom-DistinguishedName -DistinguishedName $User.DistinguishedName -ToOrganizationalUnit
            MemberOf              = $Contact.MemberOf
            DistinguishedName     = $Contact.DistinguishedName
            ManagerDN             = $null
        }
        $CachedUsers["$($Contact.DistinguishedName)"] = [PSCustomObject] $MyUser
    }
    if ($AsHashTable) {
        $CachedUsers
    } else {
        $CachedUsers.Values
    }
}
function Find-PasswordNotification {
    <#
    .SYNOPSIS
    Searches thru XML logs created by Password Solution
 
    .DESCRIPTION
    Searches thru XML logs created by Password Solution
 
    .PARAMETER SearchPath
    Path to file where the XML log is located
 
    .PARAMETER Manager
    Search thru manager escalations
 
    .EXAMPLE
    Find-PasswordNotification -SearchPath $PSScriptRoot\Search\SearchLog.xml | Format-Table
 
    .EXAMPLE
    Find-PasswordNotification -SearchPath "$PSScriptRoot\Search\SearchLog_2021-06.xml" -Manager | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $SearchPath,
        [switch] $Manager
    )
    if ($SearchPath) {
        if (Test-Path -LiteralPath $SearchPath) {
            try {
                $SummarySearch = Import-Clixml -LiteralPath $SearchPath -ErrorAction Stop
                #$SummarySearch = Get-Content -LiteralPath $SearchPath -Raw | ConvertFrom-Json
            } catch {
                Write-Color -Text "[e]", " Couldn't load the file $SearchPath", ". Skipping...", $_.Exception.Message -Color White, Yellow, White, Yellow, White, Yellow, White
            }
            if ($SummarySearch -and $Manager) {
                $SummarySearch.EmailEscalations.Values
            } elseif ($SummarySearch -and $Manager -eq $false) {
                $SummarySearch.EmailSent.Values
            }
        }
    }
}
function Start-PasswordSolution {
    <#
    .SYNOPSIS
    Starts Password Expiry Notifications for the whole forest
 
    .DESCRIPTION
    Starts Password Expiry Notifications for the whole forest
 
    .PARAMETER EmailParameters
    Parameters for Email. Uses Mailozaurr splatting behind the scenes, so it supports all options that Mailozaurr does.
 
    .PARAMETER OverwriteEmailProperty
    Property responsible for overwriting the default email field in Active Directory. Useful when the password notification has to go somewhere else than users email address.
 
    .PARAMETER UserSection
    Parameter description
 
    .PARAMETER ManagerSection
    Parameter description
 
    .PARAMETER SecuritySection
    Parameter description
 
    .PARAMETER AdminSection
    Parameter description
 
    .PARAMETER Rules
    Parameter description
 
    .PARAMETER TemplatePreExpiry
    Parameter description
 
    .PARAMETER TemplatePreExpirySubject
    Parameter description
 
    .PARAMETER TemplatePostExpiry
    Parameter description
 
    .PARAMETER TemplatePostExpirySubject
    Parameter description
 
    .PARAMETER TemplateManager
    Parameter description
 
    .PARAMETER TemplateManagerSubject
    Parameter description
 
    .PARAMETER TemplateSecurity
    Parameter description
 
    .PARAMETER TemplateSecuritySubject
    Parameter description
 
    .PARAMETER TemplateManagerNotCompliant
    Parameter description
 
    .PARAMETER TemplateManagerNotCompliantSubject
    Parameter description
 
    .PARAMETER TemplateAdmin
    Parameter description
 
    .PARAMETER TemplateAdminSubject
    Parameter description
 
    .PARAMETER Logging
    Parameter description
 
    .PARAMETER HTMLReports
    Parameter description
 
    .PARAMETER SearchPath
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][System.Collections.IDictionary] $EmailParameters,
        [string] $OverwriteEmailProperty,
        [Parameter(Mandatory)][System.Collections.IDictionary] $UserSection,
        [Parameter(Mandatory)][System.Collections.IDictionary] $ManagerSection,
        [Parameter(Mandatory)][System.Collections.IDictionary] $SecuritySection,
        [Parameter(Mandatory)][System.Collections.IDictionary] $AdminSection,
        [Parameter(Mandatory)][Array] $Rules,
        [scriptblock] $TemplatePreExpiry,
        [string] $TemplatePreExpirySubject,
        [scriptblock] $TemplatePostExpiry,
        [string] $TemplatePostExpirySubject,
        [Parameter(Mandatory)][scriptblock] $TemplateManager,
        [Parameter(Mandatory)][string] $TemplateManagerSubject,
        [Parameter(Mandatory)][scriptblock] $TemplateSecurity,
        [Parameter(Mandatory)][string] $TemplateSecuritySubject,
        [Parameter(Mandatory)][scriptblock] $TemplateManagerNotCompliant,
        [Parameter(Mandatory)][string] $TemplateManagerNotCompliantSubject,
        [Parameter(Mandatory)][scriptblock] $TemplateAdmin,
        [Parameter(Mandatory)][string] $TemplateAdminSubject,
        [Parameter(Mandatory)][System.Collections.IDictionary] $Logging,
        [Array] $HTMLReports,
        [string] $SearchPath
    )
    $TimeStart = Start-TimeLog
    $Script:Reporting = [ordered] @{}
    $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Start-PasswordSolution' -RepositoryOwner 'evotecit' -RepositoryName 'PasswordSolution'
    $TodayDate = Get-Date
    $Today = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    if (-not $Logging) {
        $Logging = @{
            ShowTime   = $true
            LogFile    = ""
            TimeFormat = "yyyy-MM-dd HH:mm:ss"
        }
    }
    $PSDefaultParameterValues = @{
        "Write-Color:LogFile"    = $Logging.LogFile
        "Write-Color:ShowTime"   = $Logging.ShowTime
        "Write-Color:TimeFormat" = $Logging.TimeFormat
    }

    if ($Logging.LogFile) {
        $FolderPath = [io.path]::GetDirectoryName($Logging.LogFile)
        if (-not (Test-Path -LiteralPath $FolderPath)) {
            $null = New-Item -Path $FolderPath -ItemType Directory -Force
        }
        $CurrentLogs = Get-ChildItem -LiteralPath $FolderPath | Sort-Object -Property CreationTime -Descending | Select-Object -Skip $Logging.LogMaximum
        if ($CurrentLogs) {
            Write-Color -Text '[i] ', "Logs directory has more than ", $Logging.LogMaximum, " log files. Cleanup required..." -Color Yellow, DarkCyan, Red, DarkCyan
            foreach ($Log in $CurrentLogs) {
                try {
                    Remove-Item -LiteralPath $Log.FullName -Confirm:$false
                    Write-Color -Text '[+] ', "Deleted ", "$($Log.FullName)" -Color Yellow, White, Green
                } catch {
                    Write-Color -Text '[-] ', "Couldn't delete log file $($Log.FullName). Error: ', "$($_.Exception.Message) -Color Yellow, White, Red
                }
            }
        }
    }

    if ($SearchPath) {
        if (Test-Path -LiteralPath $SearchPath) {
            try {
                Write-Color -Text "[i]", " Loading file ", $SearchPath -Color White, Yellow, White, Yellow, White, Yellow, White
                $SummarySearch = Import-Clixml -LiteralPath $SearchPath -ErrorAction Stop
            } catch {
                Write-Color -Text "[e]", " Couldn't load the file $SearchPath", ". Skipping...", $_.Exception.Message -Color White, Yellow, White, Yellow, White, Yellow, White
            }
        }
    }
    if (-not $SummarySearch) {
        $SummarySearch = [ordered] @{
            EmailSent        = [ordered] @{}
            EmailManagers    = [ordered] @{}
            EmailEscalations = [ordered] @{}
        }
    }

    $Summary = [ordered] @{}
    $Summary['Notify'] = [ordered] @{}
    $Summary['NotifyManager'] = [ordered] @{}
    $Summary['NotifySecurity'] = [ordered] @{}
    $Summary['Rules'] = [ordered] @{}


    $AllSkipped = [ordered] @{}
    $Locations = [ordered] @{}

    Write-Color -Text "[i]", " Starting process to find expiring users" -Color Yellow, White, Green, White, Green, White, Green, White
    $CachedUsers = Find-Password -AsHashTable -OverwriteEmailProperty $OverwriteEmailProperty
    foreach ($Rule in $Rules) {
        # Go for each rule and check if the user is in any of those rules
        if ($Rule.Enable -eq $true) {
            Write-Color -Text "[i]", " Processing rule ", $Rule.Name, ' status: ', $Rule.Enable -Color Yellow, White, Green, White, Green, White, Green, White
            # Lets create summary for the rule
            if (-not $Summary['Rules'][$Rule.Name] ) {
                $Summary['Rules'][$Rule.Name] = [ordered] @{}
            }
            # this will make sure to expand array of multiple arrays of ints if provided
            # for example: (-150..-100),(-60..0), 1, 2, 3
            $Rule.Reminders = $Rule.Reminders | ForEach-Object { $_ }
            foreach ($User in $CachedUsers.Values) {
                if ($User.Enabled -eq $false) {
                    # We don't want to have disabled users
                    continue
                }
                if ($Rule.ExcludeOU.Count -gt 0) {
                    $FoundOU = $false
                    foreach ($OU in $Rule.ExcludeOU) {
                        if ($User.OrganizationalUnit -like $OU) {
                            $FoundOU = $true
                            break
                        }
                    }
                    # if OU is found we need to exclude the user
                    if ($FoundOU) {
                        continue
                    }
                }
                if ($Rule.IncludeOU.Count -gt 0) {
                    # Rule defined that only user withi specific OU has to be found
                    $FoundOU = $false
                    foreach ($OU in $Rule.IncludeOU) {
                        if ($User.OrganizationalUnit -like $OU) {
                            $FoundOU = $true
                            break
                        }
                    }
                    if (-not $FoundOU) {
                        continue
                    }
                }
                if ($Rule.ExcludeGroup.Count -gt 0) {
                    # Rule defined that only user withi specific group has to be found
                    $FoundGroup = $false
                    foreach ($Group in $Rule.ExcludeGroup) {
                        if ($User.MemberOf -contains $Group) {
                            $FoundGroup = $true
                            break
                        }
                    }
                    # If found, we need to skip user
                    if ($FoundGroup) {
                        continue
                    }
                }
                if ($Rule.IncludeGroup.Count -gt 0) {
                    # Rule defined that only user withi specific group has to be found
                    $FoundGroup = $false
                    foreach ($Group in $Rule.IncludeGroup) {
                        if ($User.MemberOf -contains $Group) {
                            $FoundGroup = $true
                            break
                        }
                    }
                    if (-not $FoundGroup) {
                        continue
                    }
                }
                if ($Rule.IncludeName.Count -gt 0) {
                    $IncludeName = $false
                    foreach ($Name in $Rule.IncludeName) {
                        foreach ($Property in $Rule.IncludeNameProperties) {
                            if ($User.$Property -like $Name) {
                                $IncludeName = $true
                                break
                            }
                        }
                        if ($IncludeName) {
                            break
                        }
                    }
                    if (-not $IncludeName) {
                        continue
                    }
                }
                if ($Summary['Notify'][$User.DistinguishedName] -and $Summary['Notify'][$User.DistinguishedName].ProcessManagersOnly -ne $true) {
                    # User already exists in the notifications - rules are overlapping, we only take the first one
                    # We also check for ProcessManagersOnly because we don't want first rule to ignore any other rules for users
                    continue
                }
                if ($Rule.IncludePasswordNeverExpires -and $Rule.IncludeExpiring) {
                    if ($User.PasswordNeverExpires -eq $true) {
                        $DaysToPasswordExpiry = $Rule.PasswordNeverExpiresDays - $User.PasswordDays
                        $User.DaysToExpire = $DaysToPasswordExpiry
                    }
                } elseif ($Rule.IncludeExpiring) {
                    if ($User.PasswordNeverExpires -eq $true) {
                        # we skip those that never expire
                        continue
                    }
                } elseif ($Rule.IncludePasswordNeverExpires) {
                    if ($User.PasswordNeverExpires -eq $true) {
                        $DaysToPasswordExpiry = $Rule.PasswordNeverExpiresDays - $User.PasswordDays
                        $User.DaysToExpire = $DaysToPasswordExpiry
                    } else {
                        # we skip users who expire
                        continue
                    }
                } else {
                    Write-Color -Text "[i]", " Processing rule ", $Rule.Name, " doesn't include IncludePasswordNeverExpires nor IncludeExpiring so skipping." -Color Yellow, White, Green, White, Green, White, Green, White
                    continue
                }

                if ($null -eq $User.DaysToExpire) {
                    # This is to track users that our account may not have permissions over
                    if ($Logging.NotifyOnUserDaysToExpireNull) {
                        Write-Color -Text @(
                            "[i]",
                            " User ",
                            $User.DisplayName,
                            " (",
                            $User.UserPrincipalName,
                            ")",
                            " days to expire not set. ",
                            "(",
                            "Password Last Set: ",
                            $User.PasswordLastSet,
                            ")",
                            " (Password at next logon: ",
                            $User.PasswordAtNextLogon, ")"
                        ) -Color Yellow, White, Yellow, White, Yellow, White, White, White, Yellow, DarkCyan, White, Yellow, DarkCyan, White
                    }
                    # if days to expire is not set, password last set is not set either
                    # this means account either was never used or account we're using to has no permissions over that account
                    $AllSkipped[$User.DistinguishedName] = $User

                    $Location = $User.OrganizationalUnit
                    if (-not $Location) {
                        $Location = 'Default'
                    }
                    if (-not $Locations[$Location]) {
                        $Locations[$Location] = [PSCustomObject] @{
                            Location     = $Location
                            Count        = 0
                            CountExpired = 0
                            Names        = [System.Collections.Generic.List[string]]::new()
                            NamesExpired = [System.Collections.Generic.List[string]]::new()
                        }
                    }
                    if ($User.PasswordExpired) {
                        $Locations[$Location].CountExpired++
                        $Locations[$Location].NamesExpired.Add($User.SamAccountName)
                    } else {
                        $Locations[$Location].Count++
                        $Locations[$Location].Names.Add($User.SamAccountName)
                    }
                }

                # Lets find users that expire, and match our rule
                if ($null -ne $User.DaysToExpire -and $User.DaysToExpire -in $Rule.Reminders) {
                    # check if we need to notify user or just manager
                    if (-not $Rule.ProcessManagersOnly) {
                        if ($Logging.NotifyOnUserMatchingRule) {
                            Write-Color -Text "[i]", " User ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire, " " -Color Yellow, White, Yellow, White, Yellow, White, White, Blue
                        }
                        $Summary['Notify'][$User.DistinguishedName] = [ordered] @{
                            User                = $User
                            Rule                = $Rule
                            ProcessManagersOnly = $Rule.ProcessManagersOnly
                        }
                        # If we need to send an email to manager we need to update rules, just in case the user has not matched for user section
                        if ($Summary['Rules'][$Rule.Name][$User.DistinguishedName]) {
                            # User exists, update reason
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('User')
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                        } else {
                            # User doesn't exists in rules, add it
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName] = [ordered] @{
                                User                = $User
                                Rule                = $Rule
                                ProcessManagersOnly = $Rule.ProcessManagersOnly
                            }
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('User')
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                        }
                    }
                }
                # Lets find users that we need to notify manager about
                if ($null -ne $User.DaysToExpire -and $Rule.SendToManager) {
                    if ($Rule.SendToManager.Manager -and $Rule.SendToManager.Manager.Enable -eq $true -and $User.ManagerStatus -eq 'Enabled' -and $User.ManagerEmail -like "*@*") {
                        $SendToManager = $true
                        # Manager is enabled and has an email, this is standard situation for manager in AD
                        # But before we go and do that, maybe user wants to send emails to managers if those users are in specific group or OU
                        if ($Rule.SendToManager.Manager.IncludeOU.Count -gt 0) {
                            # Rule defined that only user withi specific OU has to be found
                            $FoundOU = $false
                            foreach ($OU in $Rule.SendToManager.Manager.IncludeOU) {
                                if ($User.OrganizationalUnit -like $OU) {
                                    $FoundOU = $true
                                    break
                                }
                            }
                            if (-not $FoundOU) {
                                $SendToManager = $false
                            }
                        }
                        if ($SendToManager -and $Rule.SendToManager.Manager.ExcludeOU.Count -gt 0) {
                            $FoundOU = $false
                            foreach ($OU in $Rule.SendToManager.Manager.ExcludeOU) {
                                if ($User.OrganizationalUnit -like $OU) {
                                    $FoundOU = $true
                                    break
                                }
                            }
                            # if OU is found we need to exclude the user
                            if ($FoundOU) {
                                $SendToManager = $false
                            }
                        }
                        if ($SendToManager -and $Rule.SendToManager.Manager.ExcludeGroup.Count -gt 0) {
                            # Rule defined that only user withi specific group has to be found
                            $FoundGroup = $false
                            foreach ($Group in $Rule.SendToManager.Manager.ExcludeGroup) {
                                if ($User.MemberOf -contains $Group) {
                                    $FoundGroup = $true
                                    break
                                }
                            }
                            # if Group found, we need to skip this user
                            if ($FoundGroup) {
                                $SendToManager = $false
                            }
                        }
                        if ($SendToManager -and $Rule.SendToManager.Manager.IncludeGroup.Count -gt 0) {
                            # Rule defined that only user withi specific group has to be found
                            $FoundGroup = $false
                            foreach ($Group in $Rule.SendToManager.Manager.IncludeGroup) {
                                if ($User.MemberOf -contains $Group) {
                                    $FoundGroup = $true
                                    break
                                }
                            }
                            if (-not $FoundGroup) {
                                $SendToManager = $false
                            }
                        }
                        if ($SendToManager) {
                            $SendToManager = $false
                            if ($Rule.SendToManager.Manager.Reminders.Default.Enable -eq $true -and $null -eq $Rule.SendToManager.Manager.Reminders.Default.Reminder -and $User.DaysToExpire -in $Rule.Reminders) {
                                # Use default reminder as per user, not per manager
                                $SendToManager = $true
                            } elseif ($Rule.SendToManager.Manager.Reminders.Default.Enable -eq $true -and $Rule.SendToManager.Manager.Reminders.Default.Reminder -and $User.DaysToExpire -in $Rule.SendToManager.Manager.Reminders.Default.Reminder) {
                                # User manager reminder as per manager config
                                $SendToManager = $true
                            }
                            if (-not $SendToManager -and $Rule.SendToManager.Manager.Reminders.OnDay -and $Rule.SendToManager.Manager.Reminders.OnDay.Enable -eq $true) {
                                foreach ($Day in $Rule.SendToManager.Manager.Reminders.OnDay.Days) {
                                    if ($Day -eq "$($TodayDate.DayOfWeek)") {
                                        if ($Rule.SendToManager.Manager.Reminders.OnDay.ComparisonType -eq 'lt') {
                                            if ($User.DaysToExpire -lt $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.Manager.Reminders.OnDay.ComparisonType -eq 'gt') {
                                            if ($User.DaysToExpire -gt $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.Manager.Reminders.OnDay.ComparisonType -eq 'eq') {
                                            if ($User.DaysToExpire -eq $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        } elseif ($Rule.SendtoManager.Manager.Reminders.OnDay.ComparisonType -eq 'in') {
                                            if ($User.DaysToExpire -in $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        }
                                    }
                                }
                            }
                            if (-not $SendToManager -and $Rule.SendToManager.Manager.Reminders.OnDayOfMonth -and $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Enable -eq $true) {
                                foreach ($Day in $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Days) {
                                    if ($Day -eq $TodayDate.Day) {
                                        if ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'lt') {
                                            if ($User.DaysToExpire -lt $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'gt') {
                                            if ($User.DaysToExpire -gt $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'eq') {
                                            if ($User.DaysToExpire -eq $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        } elseif ($Rule.SendtoManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'in') {
                                            if ($User.DaysToExpire -in $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) {
                                                $SendToManager = $true
                                                break
                                            }
                                        }
                                    }
                                }
                            }
                            if ($SendToManager) {
                                if ($Logging.NotifyOnUserMatchingRuleForManager) {
                                    Write-Color -Text "[i]", " User (manager rule) ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire, " " -Color Yellow, White, Yellow, White, Yellow, White, White, Blue
                                }
                                <#
                                $Summary['Notify'][$User.DistinguishedName] = [ordered] @{
                                    User = $User
                                    Rule = $Rule
                                    ProcessManagersOnly = $Rule.ProcessManagersOnly
                                }
                                #>

                                # If we need to send an email to manager we need to update rules, just in case the user has not matched for user section
                                if ($Summary['Rules'][$Rule.Name][$User.DistinguishedName]) {
                                    # User exists, update reason
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('Manager')
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                                } else {
                                    # User doesn't exists in rules, add it
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName] = [ordered] @{
                                        User                = $User
                                        Rule                = $Rule
                                        ProcessManagersOnly = $Rule.ProcessManagersOnly
                                    }
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('Manager')
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                                }

                                # Push manager to list
                                $Splat = [ordered] @{
                                    SummaryDictionary = $Summary['NotifyManager']
                                    Type              = 'ManagerDefault'
                                    ManagerType       = 'Ok'
                                    Key               = $User.ManagerDN
                                    User              = $User
                                    Rule              = $Rule
                                }
                                Add-ManagerInformation @Splat
                            }
                        }
                    }
                }
                # Lets find users that have no manager, manager is not enabled or manager has no email
                if ($Rule.SendToManager -and $Rule.SendToManager.ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.Enable -eq $true -and $Rule.SendToManager.ManagerNotCompliant.Manager) {
                    # Not compliant (missing, disabled, no email), covers all the below options
                    if ($Rule.SendToManager.ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.Enable -and $Rule.SendToManager.ManagerNotCompliant.Manager) {
                        $ManagerNotCompliant = $true
                        # But before we go and do that, maybe user wants to send emails to managers only if those users are in specific group or OU
                        if ($Rule.SendToManager.ManagerNotCompliant.IncludeOU.Count -gt 0) {
                            # Rule defined that only user withi specific OU has to be found
                            $FoundOU = $false
                            foreach ($OU in $Rule.SendToManager.ManagerNotCompliant.IncludeOU) {
                                if ($User.OrganizationalUnit -like $OU) {
                                    $FoundOU = $true
                                    break
                                }
                            }
                            if (-not $FoundOU) {
                                $ManagerNotCompliant = $false
                            }
                        }
                        if ($ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.ExcludeOU.Count -gt 0) {
                            $FoundOU = $false
                            foreach ($OU in $Rule.SendToManager.ManagerNotCompliant.ExcludeOU) {
                                if ($User.OrganizationalUnit -like $OU) {
                                    $FoundOU = $true
                                    break
                                }
                            }
                            # if OU is found we need to exclude the user
                            if ($FoundOU) {
                                $ManagerNotCompliant = $false
                            }
                        }
                        if ($ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.ExcludeGroup.Count -gt 0) {
                            # Rule defined that only user withi specific group has to be found
                            $FoundGroup = $false
                            foreach ($Group in $Rule.SendToManager.ManagerNotCompliant.ExcludeGroup) {
                                if ($User.MemberOf -contains $Group) {
                                    $FoundGroup = $true
                                    break
                                }
                            }
                            # if Group found, we need to skip this user
                            if ($FoundGroup) {
                                $ManagerNotCompliant = $false
                            }
                        }
                        if ($ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.IncludeGroup.Count -gt 0) {
                            # Rule defined that only user withi specific group has to be found
                            $FoundGroup = $false
                            foreach ($Group in $Rule.SendToManager.ManagerNotCompliant.IncludeGroup) {
                                if ($User.MemberOf -contains $Group) {
                                    $FoundGroup = $true
                                    break
                                }
                            }
                            if (-not $FoundGroup) {
                                $ManagerNotCompliant = $false
                            }
                        }

                        if ($Rule.SendToManager.ManagerNotCompliant.Reminders) {
                            $ManagerNotCompliant = $false
                            if ($Rule.SendToManager.ManagerNotCompliant.Reminders.Default -and $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Enable -eq $true) {
                                $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder = $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder | ForEach-Object { $_ }
                                if ($User.DaysToExpire -in $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder) {
                                    $ManagerNotCompliant = $true
                                }
                            }
                            if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay -and $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Enable -eq $true) {
                                foreach ($Day in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Days) {
                                    if ($Day -eq "$($TodayDate.DayOfWeek)") {
                                        if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'lt') {
                                            if ($User.DaysToExpire -lt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'gt') {
                                            if ($User.DaysToExpire -gt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'eq') {
                                            if ($User.DaysToExpire -eq $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        } elseif ($Rule.SendtoManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'in') {
                                            if ($User.DaysToExpire -in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        }
                                    }
                                }
                            }
                            if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth -and $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Enable -eq $true) {
                                foreach ($Day in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Days) {
                                    if ($Day -eq $TodayDate.Day) {
                                        if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'lt') {
                                            if ($User.DaysToExpire -lt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'gt') {
                                            if ($User.DaysToExpire -gt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'eq') {
                                            if ($User.DaysToExpire -eq $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        } elseif ($Rule.SendtoManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'in') {
                                            if ($User.DaysToExpire -in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) {
                                                $ManagerNotCompliant = $true
                                                break
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($ManagerNotCompliant -eq $true) {
                            $ManagerNotCompliantMatched = $false
                            if ($Rule.SendToManager.ManagerNotCompliant.MissingEmail -and $User.ManagerStatus -in 'Enabled, bad email', 'No email') {
                                # Manager is enabled but missing email
                                $Splat = [ordered] @{
                                    SummaryDictionary = $Summary['NotifyManager']
                                    Type              = 'ManagerNotCompliant'
                                    ManagerType       = if ($User.ManagerStatus -eq 'Enabled, bad email') { 'Manager has bad email' } else { 'Manager has no email' }
                                    Key               = $Rule.SendToManager.ManagerNotCompliant.Manager
                                    User              = $User
                                    Rule              = $Rule

                                }
                                Add-ManagerInformation @Splat

                                $ManagerNotCompliantMatched = $true
                            } elseif ($Rule.SendToManager.ManagerNotCompliant.Disabled -and $User.ManagerStatus -eq 'Disabled') {
                                # Manager is disabled, regardless if he/she has email
                                $Splat = [ordered] @{
                                    SummaryDictionary = $Summary['NotifyManager']
                                    Type              = 'ManagerNotCompliant'
                                    ManagerType       = 'Manager disabled'
                                    Key               = $Rule.SendToManager.ManagerNotCompliant.Manager
                                    User              = $User
                                    Rule              = $Rule

                                }
                                Add-ManagerInformation @Splat

                                $ManagerNotCompliantMatched = $true
                            } elseif ($Rule.SendToManager.ManagerNotCompliant.LastLogon -and $User.ManagerLastLogonDays -ge $Rule.SendToManager.ManagerNotCompliant.LastLogonDays) {
                                # Manager Last Logon over X days
                                $Splat = [ordered] @{
                                    SummaryDictionary = $Summary['NotifyManager']
                                    Type              = 'ManagerNotCompliant'
                                    ManagerType       = 'Manager not logging in'
                                    Key               = $Rule.SendToManager.ManagerNotCompliant.Manager
                                    User              = $User
                                    Rule              = $Rule

                                }
                                Add-ManagerInformation @Splat

                                $ManagerNotCompliantMatched = $true
                            } elseif ($Rule.SendToManager.ManagerNotCompliant.Missing -and $User.ManagerStatus -eq 'Missing') {
                                # Manager is missing
                                $Splat = [ordered] @{
                                    SummaryDictionary = $Summary['NotifyManager']
                                    Type              = 'ManagerNotCompliant'
                                    ManagerType       = 'Manager not set'
                                    Key               = $Rule.SendToManager.ManagerNotCompliant.Manager
                                    User              = $User
                                    Rule              = $Rule

                                }
                                Add-ManagerInformation @Splat

                                $ManagerNotCompliantMatched = $true
                            }

                            if ($ManagerNotCompliantMatched) {
                                if ($Logging.NotifyOnUserMatchingRuleForManagerNotCompliant) {
                                    Write-Color -Text "[i]", " User (manager not compliant rule) ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire, " " -Color Yellow, White, Yellow, White, Yellow, White, White, Blue
                                }
                                # If we need to send an email to manager we need to update rules, just in case the user has not matched for user section
                                if ($Summary['Rules'][$Rule.Name][$User.DistinguishedName]) {
                                    # User exists, update reason
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('Manager Not Compliant')
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                                } else {
                                    # User doesn't exists in rules, add it
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName] = [ordered] @{
                                        User                = $User
                                        Rule                = $Rule
                                        ProcessManagersOnly = $Rule.ProcessManagersOnly
                                    }
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('Manager Not Compliant')
                                    $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                                }
                            } else {
                                if ($User.ManagerStatus -eq 'Enabled') {
                                    # do nothing
                                } else {
                                    # This shouldn't happen, but just in case - we can log if this happens
                                    if ($Logging.NotifyOnUserMatchingRuleForManagerNotCompliant) {
                                        Write-Color -Text "[i]", " User (manager not compliant rule not processed) ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire, " manager status: ", $User.ManagerStatus -Color Yellow, White, Yellow, White, Yellow, White, White, Blue
                                    }
                                }
                            }
                        }
                    }
                }
                # Lets find users that require escalation
                if ($null -ne $User.DaysToExpire -and $Rule.SendToManager -and $Rule.SendToManager.SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.Enable -eq $true -and $Rule.SendToManager.SecurityEscalation.Manager) {
                    $SecurityEscalation = $true
                    if ($Rule.SendToManager.SecurityEscalation.IncludeOU.Count -gt 0) {
                        # Rule defined that only user withi specific OU has to be found
                        $FoundOU = $false
                        foreach ($OU in $Rule.SendToManager.SecurityEscalation.IncludeOU) {
                            if ($User.OrganizationalUnit -like $OU) {
                                $FoundOU = $true
                                break
                            }
                        }
                        if (-not $FoundOU) {
                            $SecurityEscalation = $false
                        }
                    }
                    if ($SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.ExcludeOU.Count -gt 0) {
                        $FoundOU = $false
                        foreach ($OU in $Rule.SendToManager.SecurityEscalation.ExcludeOU) {
                            if ($User.OrganizationalUnit -like $OU) {
                                $FoundOU = $true
                                break
                            }
                        }
                        # if OU is found we need to exclude the user
                        if ($FoundOU) {
                            $SecurityEscalation = $false
                        }
                    }
                    if ($SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.ExcludeGroup.Count -gt 0) {
                        # Rule defined that only user withi specific group has to be found
                        $FoundGroup = $false
                        foreach ($Group in $Rule.SendToManager.SecurityEscalation.ExcludeGroup) {
                            if ($User.MemberOf -contains $Group) {
                                $FoundGroup = $true
                                break
                            }
                        }
                        # if Group found, we need to skip this user
                        if ($FoundGroup) {
                            $SecurityEscalation = $false
                        }
                    }
                    if ($SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.IncludeGroup.Count -gt 0) {
                        # Rule defined that only user withi specific group has to be found
                        $FoundGroup = $false
                        foreach ($Group in $Rule.SendToManager.SecurityEscalation.IncludeGroup) {
                            if ($User.MemberOf -contains $Group) {
                                $FoundGroup = $true
                                break
                            }
                        }
                        if (-not $FoundGroup) {
                            $SecurityEscalation = $false
                        }
                    }
                    if ($Rule.SendToManager.SecurityEscalation.Reminders) {
                        $SecurityEscalation = $false
                        if ($Rule.SendToManager.SecurityEscalation.Reminders.Default -and $Rule.SendToManager.SecurityEscalation.Reminders.Default.Enable -eq $true) {
                            $Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder = $Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder | ForEach-Object { $_ }
                            if ($User.DaysToExpire -in $Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder) {
                                $SecurityEscalation = $true
                            }
                        }
                        if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay -and $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Enable -eq $true) {
                            foreach ($Day in $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Days) {
                                if ($Day -eq "$($TodayDate.DayOfWeek)") {
                                    if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'lt') {
                                        if ($User.DaysToExpire -lt $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'gt') {
                                        if ($User.DaysToExpire -gt $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'eq') {
                                        if ($User.DaysToExpire -eq $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    } elseif ($Rule.SendtoManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'in') {
                                        if ($User.DaysToExpire -in $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    }
                                }
                            }
                        }
                        if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth -and $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Enable -eq $true) {
                            foreach ($Day in $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Days) {
                                if ($Day -eq $TodayDate.Day) {
                                    if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'lt') {
                                        if ($User.DaysToExpire -lt $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'gt') {
                                        if ($User.DaysToExpire -gt $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'eq') {
                                        if ($User.DaysToExpire -eq $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    } elseif ($Rule.SendtoManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'in') {
                                        if ($User.DaysToExpire -in $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) {
                                            $SecurityEscalation = $true
                                            break
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if ($SecurityEscalation) {
                        if ($Logging.NotifyOnUserMatchingRuleForSecurityEscalation) {
                            Write-Color -Text "[i]", " User (security escalation) ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire, " " -Color Yellow, White, Yellow, White, Yellow, White, White, Blue
                        }
                        # If we need to send an email to manager we need to update rules, just in case the user has not matched for user section
                        if ($Summary['Rules'][$Rule.Name][$User.DistinguishedName]) {
                            # User exists, update reason
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('Security esclation')
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                        } else {
                            # User doesn't exists in rules, add it
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName] = [ordered] @{
                                User                = $User
                                Rule                = $Rule
                                ProcessManagersOnly = $Rule.ProcessManagersOnly
                            }
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleOptions.Add('Security esclation')
                            $Summary['Rules'][$Rule.Name][$User.DistinguishedName].User.RuleName = $Rule.Name
                        }

                        $Splat = [ordered] @{
                            SummaryDictionary = $Summary['NotifySecurity']
                            Type              = 'Security'
                            ManagerType       = 'Escalation'
                            Key               = $Rule.SendToManager.SecurityEscalation.Manager
                            User              = $User
                            Rule              = $Rule
                        }
                        Add-ManagerInformation @Splat
                    }
                }
            }
        } else {
            Write-Color -Text "[i]", " Processing rule ", $Rule.Name, ' status: ', $Rule.Enable -Color Red, White, Red, White, Red, White, Red, White
        }
    }

    if ($UserSection.Enable) {
        Write-Color -Text "[i] Sending notifications to users " -Color White, Yellow, White, Yellow, White, Yellow, White
        $CountUsers = 0
        [Array] $SummaryUsersEmails = foreach ($Notify in $Summary['Notify'].Values) {
            $CountUsers++
            $User = $Notify.User
            $Rule = $Notify.Rule

            # This shouldn't happen, but just in case, to be removed later on, as ProcessManagerOnly is skipping earlier on
            if ($Notify.ProcessManagersOnly -eq $true) {
                if ($Logging.NotifyOnSkipUserManagerOnly) {
                    Write-Color -Text "[i]", " Skipping User (Manager Only - $($Rule.Name)) ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire -Color Yellow, White, Magenta, White, Magenta, White, White, Blue
                }
                continue
            }

            $EmailSplat = [ordered] @{}

            if ($Notify.User.DaysToExpire -ge 0) {
                if ($Notify.Rule.TemplatePreExpiry) {
                    # User uses template per rule
                    $EmailSplat.Template = $Notify.Rule.TemplatePreExpiry
                } elseif ($TemplatePreExpiry) {
                    # User uses global template
                    $EmailSplat.Template = $TemplatePreExpiry
                } else {
                    # User uses built-in template
                    $EmailSplat.Template = {

                    }
                }
                if ($Notify.Rule.TemplatePreExpirySubject) {
                    $EmailSplat.Subject = $Notify.Rule.TemplatePreExpirySubject
                } elseif ($TemplatePreExpirySubject) {
                    $EmailSplat.Subject = $TemplatePreExpirySubject
                } else {
                    $EmailSplat.Subject = '[Password] Your password will expire on $DateExpiry ($TimeToExpire days)'
                }
            } else {
                if ($Notify.Rule.TemplatePostExpiry) {
                    $EmailSplat.Template = $Notify.Rule.TemplatePostExpiry
                } elseif ($TemplatePostExpiry) {
                    $EmailSplat.Template = $TemplatePostExpiry
                } else {
                    $EmailSplat.Template = {

                    }
                }
                if ($Notify.Rule.TemplatePostExpirySubject) {
                    $EmailSplat.Subject = $Notify.Rule.TemplatePostExpirySubject
                } elseif ($TemplatePostExpirySubject) {
                    $EmailSplat.Subject = $TemplatePostExpirySubject
                } else {
                    $EmailSplat.Subject = '[Password] Your password expired on $DateExpiry ($TimeToExpire days ago)'
                }
            }
            $EmailSplat.User = $Notify.User
            $EmailSplat.EmailParameters = $EmailParameters

            if ($UserSection.SendToDefaultEmail -ne $true) {
                $EmailSplat.EmailParameters.To = $Notify.User.EmailAddress
            } else {
                $EmailSplat.EmailParameters.To = $UserSection.DefaultEmail
            }
            if ($Notify.User.EmailAddress -like "*@*") {
                # Regardless if we send email to default email or to user, if user doesn't have email address we shouldn't send an email
                $EmailResult = Send-PasswordEmail @EmailSplat
                [PSCustomObject] @{
                    UserPrincipalName    = $EmailSplat.User.UserPrincipalName
                    SamAccountName       = $EmailSplat.User.SamAccountName
                    Domain               = $EmailSplat.User.Domain
                    Rule                 = $Notify.Rule.Name
                    Status               = $EmailResult.Status
                    StatusWhen           = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                    StatusError          = $EmailResult.Error
                    SentTo               = $EmailResult.SentTo
                    DateExpiry           = $EmailSplat.User.DateExpiry
                    DaysToExpire         = $EmailSplat.User.DaysToExpire
                    PasswordExpired      = $EmailSplat.User.PasswordExpired
                    PasswordNeverExpires = $EmailSplat.User.PasswordNeverExpires
                    PasswordLastSet      = $EmailSplat.User.PasswordLastSet
                }
            } else {
                # Email not sent
                $EmailResult = @{
                    Status = $false
                    Error  = 'No email address for user'
                    SentTo = ''
                }
                [PSCustomObject] @{
                    UserPrincipalName    = $EmailSplat.User.UserPrincipalName
                    SamAccountName       = $EmailSplat.User.SamAccountName
                    Domain               = $EmailSplat.User.Domain
                    Rule                 = $Notify.Rule.Name
                    Status               = $EmailResult.Status
                    StatusWhen           = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                    StatusError          = $EmailResult.Error
                    SentTo               = $EmailResult.SentTo
                    DateExpiry           = $EmailSplat.User.DateExpiry
                    DaysToExpire         = $EmailSplat.User.DaysToExpire
                    PasswordExpired      = $EmailSplat.User.PasswordExpired
                    PasswordNeverExpires = $EmailSplat.User.PasswordNeverExpires
                    PasswordLastSet      = $EmailSplat.User.PasswordLastSet
                }
            }
            if ($Logging.NotifyOnUserSend) {
                Write-Color -Text "[i]", " Sending notifications to users ", $Notify.User.DisplayName, " (", $Notify.User.EmailAddress, ")", " status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ", details: ", $EmailResult.Error -Color Yellow, White, Yellow, White, Yellow, White, White, Blue, White, Blue
            }
            if ($UserSection.SendCountMaximum -gt 0) {
                if ($UserSection.SendCountMaximum -le $CountUsers) {
                    Write-Color -Text "[i]", " Send count maximum reached. There may be more accounts that match the rule." -Color Red, DarkMagenta
                    break
                }
            }
        }
        Write-Color -Text "[i] Sending notifications to users (sent: ", $SummaryUsersEmails.Count, " out of ", $Summary['Notify'].Values.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White
    } else {
        Write-Color -Text "[i] Sending notifications to users is ", "disabled!" -Color White, Yellow, DarkMagenta
    }
    if ($ManagerSection.Enable) {
        Write-Color -Text "[i] Sending notifications to managers " -Color White, Yellow, White, Yellow, White, Yellow, White
        $CountManagers = 0
        [Array] $SummaryManagersEmails = foreach ($Manager in $Summary['NotifyManager'].Keys) {
            $CountManagers++
            if ($CachedUsers[$Manager]) {
                # This user is "findable" in AD
                $ManagerUser = $CachedUsers[$Manager]
            } else {
                # This user is provided by user in config file
                $ManagerUser = $Summary['NotifyManager'][$Manager]['Manager']
            }
            [Array] $ManagedUsers = $Summary['NotifyManager'][$Manager]['ManagerDefault'].Values.Output
            [Array] $ManagedUsersManagerNotCompliant = $Summary['NotifyManager'][$Manager]['ManagerNotCompliant'].Values.Output

            $EmailSplat = [ordered] @{}

            if ($Summary['NotifyManager'][$Manager].ManagerDefault.Count -gt 0) {
                if ($TemplateManager) {
                    # User uses global template
                    $EmailSplat.Template = $TemplateManager
                } else {
                    # User uses built-in template
                    $EmailSplat.Template = {

                    }
                }
                if ($TemplateManagerSubject) {
                    $EmailSplat.Subject = $TemplateManagerSubject
                } else {
                    $EmailSplat.Subject = "[Password Expiring] Dear Manager - Your accounts are expiring!"
                }
            } elseif ($Summary['NotifyManager'][$Manager].ManagerNotCompliant.Count -gt 0) {
                if ($TemplateManagerNotCompliant) {
                    # User uses global template
                    $EmailSplat.Template = $TemplateManagerNotCompliant
                } else {
                    # User uses built-in template
                    $EmailSplat.Template = {

                    }
                }
                if ($TemplateManagerNotCompliantSubject) {
                    $EmailSplat.Subject = $TemplateManagerNotCompliantSubject
                } else {
                    $EmailSplat.Subject = "[Password Escalation] Accounts are expiring with non-compliant manager"
                }
            }

            $EmailSplat.User = $ManagerUser
            $EmailSplat.ManagedUsers = $ManagedUsers
            $EmailSplat.ManagedUsersManagerNotCompliant = $ManagedUsersManagerNotCompliant
            $EmailSplat.EmailParameters = $EmailParameters

            if ($ManagerSection.SendToDefaultEmail -ne $true) {
                $EmailSplat.EmailParameters.To = $ManagerUser.EmailAddress
            } else {
                $EmailSplat.EmailParameters.To = $ManagerSection.DefaultEmail
            }
            if ($Logging.NotifyOnManagerSend) {
                Write-Color -Text "[i] Sending notifications to managers ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow
            }
            $EmailResult = Send-PasswordEmail @EmailSplat
            if ($Logging.NotifyOnManagerSend) {
                Write-Color -Text "[r] Sending notifications to managers ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ") (status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow
            }

            [PSCustomObject] @{
                DisplayName              = $ManagerUser.DisplayName
                SamAccountName           = $ManagerUser.SamAccountName
                Domain                   = $ManagerUser.Domain
                Status                   = $EmailResult.Status
                StatusWhen               = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                SentTo                   = $EmailResult.SentTo
                StatusError              = $EmailResult.Error
                Accounts                 = $ManagedUsers.SamAccountName
                AccountsCount            = $ManagedUsers.Count
                Template                 = 'Unknown'
                ManagerNotCompliant      = $ManagedUsersManagerNotCompliant.SamAccountName
                ManagerNotCompliantCount = $ManagedUsersManagerNotCompliant.Count
                #ManagerDisabled = $ManagedUsersManagerDisabled.SamAccountName
                #ManagerDisabledCount = $ManagedUsersManagerDisabled.Count
                #ManagerMissing = $ManagedUsersManagerMissing.SamAccountName
                #ManagerMissingCount = $ManagedUsersManagerMissing.Count
                #ManagerMissingEmail = $ManagedUsersManagerMissingEmail.SamAccountName
                #ManagerMissingEmailCount = $ManagedUsersManagerMissingEmail.Count
            }
            if ($ManagerSection.SendCountMaximum -gt 0) {
                if ($ManagerSection.SendCountMaximum -le $CountManagers) {
                    Write-Color -Text "[i]", " Send count maximum reached. There may be more managers that match the rule." -Color Red, DarkMagenta
                    break
                }
            }
        }
        Write-Color -Text "[i] Sending notifications to managers (sent: ", $SummaryManagersEmails.Count, " out of ", $Summary['NotifyManager'].Values.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White
        #Write-Color -Text "[i] Sending notifications to managers (sent: ", $SummaryManagersEmails.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White
    } else {
        Write-Color -Text "[i] Sending notifications to managers is ", "disabled!" -Color White, Yellow, DarkMagenta
    }
    if ($SecuritySection.Enable) {
        Write-Color -Text "[i] Sending notifications to security " -Color White, Yellow, White, Yellow, White, Yellow, White
        $CountSecurity = 0
        [Array] $SummaryEscalationEmails = foreach ($Manager in $Summary['NotifySecurity'].Keys) {
            $CountSecurity++
            # This user is provided by user in config file
            $ManagerUser = $Summary['NotifySecurity'][$Manager]['Manager']

            [Array] $ManagedUsers = $Summary['NotifySecurity'][$Manager]['Security'].Values.Output

            $EmailSplat = [ordered] @{}

            if ($Summary['NotifySecurity'][$Manager].Security.Count -gt 0) {
                # User uses global template
                $EmailSplat.Template = $TemplateSecurity
                if ($TemplateSecuritySubject) {
                    $EmailSplat.Subject = $TemplateSecuritySubject
                } else {
                    $EmailSplat.Subject = "[Password Expiring] Dear Security - Accounts expired"
                }
            } else {
                continue
            }

            if ($SecuritySection.AttachCSV -and $ManagedUsers.Count -gt 0) {
                $ManagedUsers | Export-Csv -LiteralPath $Env:TEMP\ManagedUsersSecurity.csv -NoTypeInformation -Force -Encoding UTF8 -ErrorAction Stop
                $EmailSplat.Attachments = @(
                    if (Test-Path -LiteralPath "$Env:TEMP\ManagedUsersSecurity.csv") {
                        "$Env:TEMP\ManagedUsersSecurity.csv"
                    }
                )
            }
            $EmailSplat.User = $ManagerUser
            $EmailSplat.ManagedUsers = $ManagedUsers | Select-Object -Property 'Status', 'DisplayName', 'Enabled', 'SamAccountName', 'Domain', 'DateExpiry', 'DaysToExpire', 'PasswordLastSet', 'PasswordExpired'
            #$EmailSplat.ManagedUsersManagerNotCompliant = $ManagedUsersManagerNotCompliant
            #$EmailSplat.ManagedUsersManagerDisabled = $ManagedUsersManagerDisabled
            #$EmailSplat.ManagedUsersManagerMissing = $ManagedUsersManagerMissing
            #$EmailSplat.ManagedUsersManagerMissingEmail = $ManagedUsersManagerMissingEmail
            $EmailSplat.EmailParameters = $EmailParameters

            if ($SecuritySection.SendToDefaultEmail -ne $true) {
                $EmailSplat.EmailParameters.To = $ManagerUser.EmailAddress
            } else {
                $EmailSplat.EmailParameters.To = $SecuritySection.DefaultEmail
            }
            if ($Logging.NotifyOnSecuritySend) {
                Write-Color -Text "[i] Sending notifications to security ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow
            }
            $EmailResult = Send-PasswordEmail @EmailSplat
            if ($Logging.NotifyOnSecuritySend) {
                Write-Color -Text "[r] Sending notifications to security ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ") (status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow
            }
            [PSCustomObject] @{
                DisplayName    = $ManagerUser.DisplayName
                SamAccountName = $ManagerUser.SamAccountName
                Domain         = $ManagerUser.Domain
                Status         = $EmailResult.Status
                StatusWhen     = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                SentTo         = $EmailResult.SentTo
                StatusError    = $EmailResult.Error
                Accounts       = $ManagedUsers.SamAccountName
                AccountsCount  = $ManagedUsers.Count
                Template       = 'Unknown'
                # ManagerNotCompliant = $ManagedUsersManagerNotCompliant.SamAccountName
                # ManagerNotCompliantCount = $ManagedUsersManagerNotCompliant.Count
                #ManagerDisabled = $ManagedUsersManagerDisabled.SamAccountName
                #ManagerDisabledCount = $ManagedUsersManagerDisabled.Count
                #ManagerMissing = $ManagedUsersManagerMissing.SamAccountName
                #ManagerMissingCount = $ManagedUsersManagerMissing.Count
                #ManagerMissingEmail = $ManagedUsersManagerMissingEmail.SamAccountName
                #ManagerMissingEmailCount = $ManagedUsersManagerMissingEmail.Count
            }
            if ($SecuritySection.SendCountMaximum -gt 0) {
                if ($SecuritySection.SendCountMaximum -le $CountSecurity) {
                    Write-Color -Text "[i]", " Send count maximum reached. There may be more managers that match the rule." -Color Red, DarkMagenta
                    break
                }
            }
        }
        Write-Color -Text "[i] Sending notifications to security (sent: ", $SummaryEscalationEmails.Count, " out of ", $Summary['NotifySecurity'].Values.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White
    } else {
        Write-Color -Text "[i] Sending notifications to security is ", "disabled!" -Color White, Yellow, DarkMagenta
    }

    $TimeEnd = Stop-TimeLog -Time $TimeStart -Option OneLiner

    $HtmlAttachments = [System.Collections.Generic.List[string]]::new()

    foreach ($Report in $HTMLReports) {
        if ($Report.Enable) {
            $ReportSettings = @{
                Report                  = $Report
                EmailParameters         = $EmailParameters
                Logging                 = $Logging
                FilePath                = $FilePath
                SearchPath              = $SearchPath
                Rules                   = $Rules
                UserSection             = $UserSection
                ManagerSection          = $ManagerSection
                SecuritySection         = $SecuritySection
                AdminSection            = $AdminSection
                CachedUsers             = $CachedUsers
                Summary                 = $Summary
                SummaryUsersEmails      = $SummaryUsersEmails
                SummaryManagersEmails   = $SummaryManagersEmails
                SummaryEscalationEmails = $SummaryEscalationEmails
                SummarySearch           = $SummarySearch
                Locations               = $Locations
                AllSkipped              = $AllSkipped
            }
            New-HTMLReport @ReportSettings

            if ($Report.AttachToEmail) {
                if (Test-Path -LiteralPath $Report.FilePath) {
                    $HtmlAttachments.Add($Report.FilePath)
                } else {
                    Write-Color -Text "[w] HTML report ", $Report.FilePath, " does not exist! Probably a temporary path was used. " -Color DarkYellow, Red, DarkYellow
                }
            }
        }
    }
    if ($SearchPath) {
        Write-Color -Text "[i]" , " Saving Search report " -Color White, Yellow, Green
        $SummarySearch['EmailSent'][$Today] = $SummaryUsersEmails
        $SummarySearch['EmailEscalations'][$Today] = $SummaryEscalationEmails
        $SummarySearch['EmailManagers'][$Today] = $SummaryManagersEmails

        try {
            $SummarySearch | Export-Clixml -LiteralPath $SearchPath
        } catch {
            Write-Color -Text "[e]", " Couldn't save to file $SearchPath", ". Error: ", $_.Exception.Message -Color White, Yellow, White, Yellow, White, Yellow, White
        }
        Write-Color -Text "[i]" , " Saving Search report ", "Done" -Color White, Yellow, Green
    }

    if ($AdminSection.Enable) {
        Write-Color -Text "[i] Sending summary information " -Color White, Yellow, White, Yellow, White, Yellow, White
        $CountSecurity = 0
        [Array] $SummaryEmail = @(
            $CountSecurity++
            # This user is provided by user in config file
            $ManagerUser = $AdminSection.Manager

            $EmailSplat = [ordered] @{}
            # User uses global template
            $EmailSplat.Template = $TemplateAdmin
            $EmailSplat.Subject = $TemplateAdminSubject
            $EmailSplat.User = $ManagerUser

            $EmailSplat.SummaryUsersEmails = $SummaryUsersEmails
            $EmailSplat.SummaryManagersEmails = $SummaryManagersEmails
            $EmailSplat.SummaryEscalationEmails = $SummaryEscalationEmails
            $EmailSplat.TimeToProcess = $TimeEnd

            $EmailSplat.EmailParameters = $EmailParameters

            $EmailSplat.EmailParameters.To = $AdminSection.Manager.EmailAddress

            if ($HtmlAttachments.Count -gt 0) {
                $EmailSplat.Attachments = $HtmlAttachments
            }
            Write-Color -Text "[i] Sending summary information ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow

            $EmailResult = Send-PasswordEmail @EmailSplat

            Write-Color -Text "[r] Sending summary information ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ") (status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow

            [PSCustomObject] @{
                DisplayName    = $ManagerUser.DisplayName
                SamAccountName = $ManagerUser.SamAccountName
                Domain         = $ManagerUser.Domain
                Status         = $EmailResult.Status
                StatusWhen     = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                SentTo         = $EmailResult.SentTo
                StatusError    = $EmailResult.Error
                Template       = 'Unknown'
            }
        )
        Write-Color -Text "[i] Sending summary information (sent: ", $SummaryEmail.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White
    } else {
        Write-Color -Text "[i] Sending summary information is ", "disabled!" -Color White, Yellow, DarkMagenta
    }
}



# Export functions and aliases as required
Export-ModuleMember -Function @('Find-Password', 'Find-PasswordNotification', 'Start-PasswordSolution') -Alias @()
# SIG # Begin signature block
# MIInPgYJKoZIhvcNAQcCoIInLzCCJysCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAG9QJlBFhoK203
# SVfNabcNUg8bYoqoER+2aFjNK67BZaCCITcwggO3MIICn6ADAgECAhAM5+DlF9hG
# /o/lYPwb8DA5MA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBa
# Fw0zMTExMTAwMDAwMDBaMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lD
# ZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
# AQoCggEBAK0OFc7kQ4BcsYfzt2D5cRKlrtwmlIiq9M71IDkoWGAM+IDaqRWVMmE8
# tbEohIqK3J8KDIMXeo+QrIrneVNcMYQq9g+YMjZ2zN7dPKii72r7IfJSYd+fINcf
# 4rHZ/hhk0hJbX/lYGDW8R82hNvlrf9SwOD7BG8OMM9nYLxj+KA+zp4PWw25EwGE1
# lhb+WZyLdm3X8aJLDSv/C3LanmDQjpA1xnhVhyChz+VtCshJfDGYM2wi6YfQMlqi
# uhOCEe05F52ZOnKh5vqk2dUXMXWuhX0irj8BRob2KHnIsdrkVxfEfhwOsLSSplaz
# vbKX7aqn8LfFqD+VFtD/oZbrCF8Yd08CAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGG
# MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEXroq/0ksuCMS1Ri6enIZ3zbcgP
# MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUA
# A4IBAQCiDrzf4u3w43JzemSUv/dyZtgy5EJ1Yq6H6/LV2d5Ws5/MzhQouQ2XYFwS
# TFjk0z2DSUVYlzVpGqhH6lbGeasS2GeBhN9/CTyU5rgmLCC9PbMoifdf/yLil4Qf
# 6WXvh+DfwWdJs13rsgkq6ybteL59PyvztyY1bV+JAbZJW58BBZurPSXBzLZ/wvFv
# hsb6ZGjrgS2U60K3+owe3WLxvlBnt2y98/Efaww2BxZ/N3ypW2168RJGYIPXJwS+
# S86XvsNnKmgR34DnDDNmvxMNFG7zfx9jEB76jRslbWyPpbdhAbHSoyahEHGdreLD
# +cOZUbcrBwjOLuZQsqf6CkUvovDyMIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1
# b5VQCDANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGln
# aUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtE
# aWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgx
# MDIyMTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT
# SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEF
# AAOCAQ8AMIIBCgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLX
# cep2nQUut4/6kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSR
# I5aQd4L5oYQjZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXi
# TWAYvqrEsq5wMWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5
# Ng2Q7+S1TqSp6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8
# vYWxYoNzQYIH5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYD
# VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYB
# BQUHAwMweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k
# aWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4
# oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJv
# b3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dEFzc3VyZWRJRFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCow
# KAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZI
# AYb9bAMwHQYDVR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaA
# FEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPz
# ItEVyCx8JSl2qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRu
# pY5a4l4kgU4QpO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKN
# JK4kxscnKqEpKBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmif
# z0DLQESlE/DmZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN
# 3fYBIM6ZMWM9CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKy
# ZqHnGKSaZFHvMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkqhkiG
# 9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw
# FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEy
# IEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoXDTIz
# MDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tpZTER
# MA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlzIEVW
# T1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjANBgkq
# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqDBqln
# r3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfFjVye
# 3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub+3ti
# i0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf3tZZ
# zO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6Ea41
# zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQABo4IB
# xTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYE
# FBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK
# BggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2Vy
# dC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQu
# ZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3
# BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
# Y29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25p
# bmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1sz4ls
# LARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsRXPHU
# F/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHINrTC
# vPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8Rj9y
# G4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6o6ES
# Jre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNpezQu
# g9ufqExx6lHYDjCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZI
# hvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
# MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNz
# dXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVow
# YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290
# IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjww
# IjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J5
# 8soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMH
# hOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6
# Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQ
# ecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4b
# A3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9
# WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCU
# tNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvo
# ZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/J
# vNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCP
# orF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMB
# Af8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXr
# oq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRt
# MGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEF
# BQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgw
# BgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cH
# vZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8
# UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTn
# f+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxU
# jG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8j
# LfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDCCBq4w
# ggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkG
# A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
# Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4X
# DTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAV
# BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVk
# IEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5M
# om2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE
# 2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWN
# lCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFo
# bjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhN
# ef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3Vu
# JyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtz
# Q87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4O
# uGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5
# sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm
# 4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIz
# tM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6
# FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qY
# rhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYB
# BQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
# QQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZ
# MBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmO
# wJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H
# 6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/
# R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzv
# qLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/ae
# sXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdm
# kfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3
# EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh
# 3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA
# 3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8
# BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsf
# gPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbAMIIEqKADAgECAhAMTWly
# S5T6PCpKPSkHgD1aMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYD
# VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH
# NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjIwOTIxMDAwMDAw
# WhcNMzMxMTIxMjM1OTU5WjBGMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNl
# cnQxJDAiBgNVBAMTG0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIyIC0gMjCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAM/spSY6xqnya7uNwQ2a26HoFIV0Mxom
# rNAcVR4eNm28klUMYfSdCXc9FZYIL2tkpP0GgxbXkZI4HDEClvtysZc6Va8z7GGK
# 6aYo25BjXL2JU+A6LYyHQq4mpOS7eHi5ehbhVsbAumRTuyoW51BIu4hpDIjG8b7g
# L307scpTjUCDHufLckkoHkyAHoVW54Xt8mG8qjoHffarbuVm3eJc9S/tjdRNlYRo
# 44DLannR0hCRRinrPibytIzNTLlmyLuqUDgN5YyUXRlav/V7QG5vFqianJVHhoV5
# PgxeZowaCiS+nKrSnLb3T254xCg/oxwPUAY3ugjZNaa1Htp4WB056PhMkRCWfk3h
# 3cKtpX74LRsf7CtGGKMZ9jn39cFPcS6JAxGiS7uYv/pP5Hs27wZE5FX/NurlfDHn
# 88JSxOYWe1p+pSVz28BqmSEtY+VZ9U0vkB8nt9KrFOU4ZodRCGv7U0M50GT6Vs/g
# 9ArmFG1keLuY/ZTDcyHzL8IuINeBrNPxB9ThvdldS24xlCmL5kGkZZTAWOXlLimQ
# prdhZPrZIGwYUWC6poEPCSVT8b876asHDmoHOWIZydaFfxPZjXnPYsXs4Xu5zGcT
# B5rBeO3GiMiwbjJ5xwtZg43G7vUsfHuOy2SJ8bHEuOdTXl9V0n0ZKVkDTvpd6kVz
# HIR+187i1Dp3AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/
# BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEE
# AjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8w
# HQYDVR0OBBYEFGKK3tBh/I8xFO2XC809KpQU31KcMFoGA1UdHwRTMFEwT6BNoEuG
# SWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQw
# OTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKG
# TGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJT
# QTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIB
# AFWqKhrzRvN4Vzcw/HXjT9aFI/H8+ZU5myXm93KKmMN31GT8Ffs2wklRLHiIY1UJ
# RjkA/GnUypsp+6M/wMkAmxMdsJiJ3HjyzXyFzVOdr2LiYWajFCpFh0qYQitQ/Bu1
# nggwCfrkLdcJiXn5CeaIzn0buGqim8FTYAnoo7id160fHLjsmEHw9g6A++T/350Q
# p+sAul9Kjxo6UrTqvwlJFTU2WZoPVNKyG39+XgmtdlSKdG3K0gVnK3br/5iyJpU4
# GYhEFOUKWaJr5yI+RCHSPxzAm+18SLLYkgyRTzxmlK9dAlPrnuKe5NMfhgFknADC
# 6Vp0dQ094XmIvxwBl8kZI4DXNlpflhaxYwzGRkA7zl011Fk+Q5oYrsPJy8P7mxNf
# arXH4PMFw1nfJ2Ir3kHJU7n/NBBn9iYymHv+XEKUgZSCnawKi8ZLFUrTmJBFYDOA
# 4CPe+AOk9kVH5c64A0JH6EE2cXet/aLol3ROLtoeHYxayB6a1cLwxiKoT5u92Bya
# UcQvmvZfpyeXupYuhVfAYOd4Vn9q78KVmksRAsiCnMkaBXy6cbVOepls9Oie1FqY
# yJ+/jbsYXEP10Cro4mLueATbvdH7WwqocH7wl4R44wgDXUcsY6glOJcB0j862uXl
# 9uab3H4szP8XTE0AotjWAQ64i+7m4HJViSwnGWH2dwGMMYIFXTCCBVkCAQEwgYYw
# cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk
# IElEIENvZGUgU2lnbmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzANBglghkgBZQME
# AgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEM
# BgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqG
# SIb3DQEJBDEiBCCuQ5dV2qrEROjWnXvr3jdhYDftGKGIqST40hvGUum40zANBgkq
# hkiG9w0BAQEFAASCAQAPDGuAwzxv0CYaUafNIo+/QtlRTKNbXy3yBUoayBuJUxuH
# PwkwQBeuaxsiqZ5k+W+HB+zVm1CZgV/k+8xqs5DGUaHwKvh/xax5I6KFoa08lwJ/
# vmRpNb7RWzsSA2UqjpYCpsU234M9oGlnbebTjZAH0PphBugU6FGXLxoRNAIR02Ny
# YdA0lEHiiunufrhazE23dHVsTueLFamSVr9yDr0Zn3gwMeKADWjwqgRJdmZf15YG
# wHbvzRy8XV4GZrEcZvhJ8aSFeydTfK5ZayOBEOSNSKC8sdnEZfOimYrQeu1AS5+6
# 5yXcRob7dJfEO6qCgCWAXI6usqKhmgzdBKkIf6WHoYIDIDCCAxwGCSqGSIb3DQEJ
# BjGCAw0wggMJAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0
# LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hB
# MjU2IFRpbWVTdGFtcGluZyBDQQIQDE1pckuU+jwqSj0pB4A9WjANBglghkgBZQME
# AgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X
# DTIyMDkyMjIwMDgyMlowLwYJKoZIhvcNAQkEMSIEIEV8WRpJwwb9fwev6MnFgbU7
# /FGrij8lxfk8E6r4kJ5kMA0GCSqGSIb3DQEBAQUABIICALBZWlLMb13MFBsv/DEL
# hVS97DrhyESEc5eTPGHMBPK01eirKEmO+v9n8LOYpKdNf2+ZQy+0fA6X14BCsBAP
# E7jAxZlKp8nfmNPrndxgNZCRtxxGaY2a5o1x9tBkEi0qAcoGTdHKIoMB2kSFCuG0
# CGgAGi9hDNrP5WjjjMD2sGK9Yc9sBskt2QU3ti2wV5Hk55SZKtcnvXXxab6kQpVP
# Kg69xqbvQsDl1TiXUiSPHP8rx1rynMQXs2sV+iJWxNwdMBB2dIy/b9MagLr6akIi
# UJZJ98sIinL9tixue5vO8/wp00SfpRqUCu5wMeouXSDnvnaHd0sz3q2qFrABMXUV
# yZpZdTyeUngzwGC6tbkyTNekhIcSKtnJ2JOYVE3K4sXNI8KMAcCBQqi2tQaFgouh
# /QB383WQrXQ1d4YR2Crq8exF+OOwiAD8t+hE4EvVup+aKisV5IJcLoKsibLytkVx
# cR+HdMDJNIDOw73DoozOZ9WTYJahnjeanvh/KVKF8ehJ2DaO221nj0Aj2nOfD4Q5
# PuQG+/RL7j+6qWGCGkl89jTA+352o2FeLK4Ymryokud1URwnYbl3qhHx7Kk1ua2/
# UBeeJE12Ih64EtK1zzFamKP6pPi8oW/SmGifsucxMhmtsC/bemGi+3G1zfIMsIic
# pBePLIzLu/pPy47a8+y0I8fz
# SIG # End signature block