ExchangeEssentials.psm1

function Convert-ExchangeEmail { 
    <#
    .SYNOPSIS
    Function that helps converting Exchange email address list into readable, exportable format.
 
    .DESCRIPTION
        Function that helps converting Exchange email address list into readable, exportable format.
 
    .PARAMETER Emails
    List of emails as available in Exchange or Exchange Online, otherwise known as proxy addresses list
 
    .PARAMETER Separator
 
    .PARAMETER RemoveDuplicates
 
    .PARAMETER RemovePrefix
 
    .PARAMETER AddSeparator
 
    .EXAMPLE
 
    $Emails = @()
    $Emails += 'SIP:test@email.com'
    $Emails += 'SMTP:elo@maiu.com'
    $Emails += 'sip:elo@maiu.com'
    $Emails += 'Spo:dfte@sdsd.com'
    $Emails += 'SPO:myothertest@sco.com'
 
    Convert-ExchangeEmail -Emails $Emails -RemovePrefix -RemoveDuplicates -AddSeparator
 
    .NOTES
    General notes
    #>


    [CmdletBinding()]
    param(
        [string[]] $Emails,
        [string] $Separator = ', ',
        [switch] $RemoveDuplicates,
        [switch] $RemovePrefix,
        [switch] $AddSeparator
    )

    if ($RemovePrefix) {

        $Emails = $Emails -replace 'smtp:', '' -replace 'sip:', '' -replace 'spo:', ''
    }
    if ($RemoveDuplicates) {
        $Emails = $Emails | Sort-Object -Unique
    }
    if ($AddSeparator) {
        $Emails = $Emails -join $Separator
    }
    return $Emails
}
function Convert-ExchangeSize { 
    [cmdletbinding()]
    param(
        [validateset("Bytes", "KB", "MB", "GB", "TB")][string]$To = 'MB',
        [string]$Size,
        [int]$Precision = 4,
        [switch]$Display,
        [string]$Default = 'N/A'
    )
    if ([string]::IsNullOrWhiteSpace($Size)) {
        return $Default
    }
    $Pattern = [Regex]::new('(?<=\()([0-9]*[,.].*[0-9])')  
    $Value = ($Size | Select-String $Pattern -AllMatches).Matches.Value

    if ($null -ne $Value) {
        $Value = $Value.Replace(',', '').Replace('.', '')
    }

    switch ($To) {
        "Bytes" {
            return $value
        }
        "KB" {
            $Value = $Value / 1KB
        }
        "MB" {
            $Value = $Value / 1MB
        }
        "GB" {
            $Value = $Value / 1GB
        }
        "TB" {
            $Value = $Value / 1TB
        }
    }

    if ($Display) {
        return "$([Math]::Round($value,$Precision,[MidPointRounding]::AwayFromZero)) $To"
    }
    else {
        return [Math]::Round($value, $Precision, [MidPointRounding]::AwayFromZero)
    }
}
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 Canonical Name (CN)
 
    .PARAMETER ToCanonicalName
    Converts DistinguishedName to Canonical Name
 
    .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
 
    .EXAMPLEE
    ConvertFrom-DistinguishedName -DistinguishedName 'DC=ad,DC=evotec,DC=xyz' -ToCanonicalName
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName
    ConvertFrom-DistinguishedName -DistinguishedName 'CN=test,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName
 
    Output:
    ad.evotec.xyz
    ad.evotec.xyz\Production\Users
    ad.evotec.xyz\Production\Users\test
 
    .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')]
        [Parameter(ParameterSetName = 'ToCanonicalName')]
        [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,
        [Parameter(ParameterSetName = 'ToCanonicalName')][switch] $ToCanonicalName
    )
    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=', ''
                }
            }
            elseif ($ToCanonicalName) {
                $Domain = $null
                $Rest = $null
                foreach ($O in $Distinguished -split '(?<!\\),') {
                    if ($O -match '^DC=') {
                        $Domain += $O.Substring(3) + '.'
                    }
                    else {
                        $Rest = $O.Substring(3) + '\' + $Rest
                    }
                }
                if ($Domain -and $Rest) {
                    $Domain.Trim('.') + '\' + ($Rest.TrimEnd('\') -replace '\\,', ',')
                }
                elseif ($Domain) {
                    $Domain.Trim('.')
                }
                elseif ($Rest) {
                    $Rest.TrimEnd('\') -replace '\\,', ','
                }
            }
            else {
                $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'

                $Found = $Distinguished -match $Regex
                if ($Found) {
                    $Matches.cn
                }
            }
        }
    }
}
function Copy-Dictionary { 
    <#
    .SYNOPSIS
    Copies dictionary/hashtable
 
    .DESCRIPTION
    Copies dictionary uusing PS Serializer. Replaces usage of BinnaryFormatter due to no support in PS 7.4
 
    .PARAMETER Dictionary
    Dictionary to copy
 
    .EXAMPLE
    $Test = [ordered] @{
        Test = 'Test'
        Test1 = @{
            Test2 = 'Test2'
            Test3 = @{
                Test4 = 'Test4'
            }
        }
        Test2 = @(
            "1", "2", "3"
        )
        Test3 = [PSCustomObject] @{
            Test4 = 'Test4'
            Test5 = 'Test5'
        }
    }
 
    $New1 = Copy-Dictionary -Dictionary $Test
    $New1
 
    .NOTES
 
    #>

    [alias('Copy-Hashtable', 'Copy-OrderedHashtable')]
    [cmdletbinding()]
    param(
        [System.Collections.IDictionary] $Dictionary
    )
    $clone = [System.Management.Automation.PSSerializer]::Serialize($Dictionary, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($clone)
}
function Get-FileName { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Extension
    Parameter description
 
    .PARAMETER Temporary
    Parameter description
 
    .PARAMETER TemporaryFileOnly
    Parameter description
 
    .EXAMPLE
    Get-FileName -Temporary
    Output: 3ymsxvav.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary
    Output: C:\Users\pklys\AppData\Local\Temp\tmpD74C.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary -Extension 'xlsx'
    Output: C:\Users\pklys\AppData\Local\Temp\tmp45B6.xlsx
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $Extension = 'tmp',
        [switch] $Temporary,
        [switch] $TemporaryFileOnly
    )

    if ($Temporary) {

        return [io.path]::Combine([System.IO.Path]::GetTempPath(), "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension")
    }
    if ($TemporaryFileOnly) {

        return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension"
    }
}
function Get-GitHubVersion { 
    <#
    .SYNOPSIS
    Get the latest version of a GitHub repository and compare with local version
 
    .DESCRIPTION
    Get the latest version of a GitHub repository and compare with local version
 
    .PARAMETER Cmdlet
    Cmdlet to find module for
 
    .PARAMETER RepositoryOwner
    Repository owner
 
    .PARAMETER RepositoryName
    Repository name
 
    .EXAMPLE
    Get-GitHubVersion -Cmdlet 'Start-DelegationModel' -RepositoryOwner 'evotecit' -RepositoryName 'DelegationModel'
 
    .NOTES
    General notes
    #>

    [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)"
        }
    }
    else {
        "Current: Unknown"
    }
}
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] $PreferWritable,
        [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 -Writable:$PreferWritable.IsPresent

                $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 delivering a lot of additional features for easier color options.
 
    .DESCRIPTION
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    It provides:
    - Easy manipulation of colors,
    - Logging output to file (log)
    - Nice formatting options out of the box.
    - Ability to use aliases for parameters
 
    .PARAMETER Text
    Text to display on screen and write to log file if specified.
    Accepts an array of strings.
 
    .PARAMETER Color
    Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER BackGroundColor
    Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER StartTab
    Number of tabs to add before text. Default is 0.
 
    .PARAMETER LinesBefore
    Number of empty lines before text. Default is 0.
 
    .PARAMETER LinesAfter
    Number of empty lines after text. Default is 0.
 
    .PARAMETER StartSpaces
    Number of spaces to add before text. Default is 0.
 
    .PARAMETER LogFile
    Path to log file. If not specified no log file will be created.
 
    .PARAMETER DateTimeFormat
    Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss
 
    .PARAMETER LogTime
    If set to $true it will add time to log file. Default is $true.
 
    .PARAMETER LogRetry
    Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2.
 
    .PARAMETER Encoding
    Encoding of the log file. Default is Unicode.
 
    .PARAMETER ShowTime
    Switch to add time to console output. Default is not set.
 
    .PARAMETER NoNewLine
    Switch to not add new line at the end of the output. Default is not set.
 
    .PARAMETER NoConsoleOutput
    Switch to not output to console. Default all output goes to console.
 
    .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
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    Write-Color -t "my text" -c yellow -b green
    Write-Color -text "my text" -c red
 
    .EXAMPLE
    Write-Color -Text "TestujÄ™ czy siÄ™ Å‚adnie zapisze, czy bÄ™dÄ… problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput
 
    .NOTES
    Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
    Project support: https://github.com/EvotecIT/PSWriteColor
    Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    #>

    [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,
        [alias('HideConsole')][switch] $NoConsoleOutput
    )
    if (-not $NoConsoleOutput) {
        $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 
            } 
        } # Add empty line before
        if ($StartTab -ne 0) {
            for ($i = 0; $i -lt $StartTab; $i++) {
                Write-Host -Object "`t" -NoNewline 
            } 
        }  # Add TABS before text
        if ($StartSpaces -ne 0) {
            for ($i = 0; $i -lt $StartSpaces; $i++) {
                Write-Host -Object ' ' -NoNewline 
            } 
        }  # Add SPACES before text
        if ($ShowTime) {
            Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline 
        } # Add Time before output
        if ($Text.Count -ne 0) {
            if ($Color.Count -ge $Text.Count) {
                # the real deal coloring
                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 
        } # Support for no new line
        if ($LinesAfter -ne 0) {
            for ($i = 0; $i -lt $LinesAfter; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        }  # Add empty line after
    }
    if ($Text.Count -and $LogFile) {
        # Save to file
        $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) {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))"
                }
                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 (22621)' = 'Windows 11 22H2'
            '10.0 (22000)' = 'Windows 11 21H2'
            '10.0 (19045)' = 'Windows 10 22H2'
            '10.0 (19044)' = 'Windows 10 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.22621'   = 'Windows 11 22H2'
            '10.0.22000'   = 'Windows 11 21H2'
            '10.0.19045'   = 'Windows 10 22H2'
            '10.0.19044'   = 'Windows 10 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"

            '22621'        = 'Windows 11 22H2'
            '22000'        = 'Windows 11 21H2'
            '19045'        = 'Windows 10 22H2'
            '19044'        = 'Windows 10 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 = $OperatingSystemVersion
        }
    }
    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 = $OperatingSystemVersion
        }
    }
    else {
        $System = $OperatingSystem
    }
    if ($System) {
        $System
    }
    else {
        'Unknown'
    }
}
function Copy-DictionaryManual { 
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Dictionary
    )

    $clone = [ordered] @{}
    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 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.com/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 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
}
$Script:ReportMailbox = [ordered] @{
    Name       = 'Mailbox'
    Enabled    = $true
    Execute    = {
        Get-MyMailbox
    }
    Processing = {
    }
    Summary    = {
    }
    Variables  = @{
    }
    Solution   = {
        New-HTMLTable -DataTable $Script:Reporting['Mailbox']['Data'] -Filtering {
        }
    }
}
$Script:ReportMailboxProblems = [ordered] @{
    Name       = 'MailboxProblems'
    Enabled    = $true
    Execute    = {
        Get-MyMailboxProblems -Local
    }
    Processing = {
    }
    Summary    = {
    }
    Variables  = @{
    }
    Solution   = {
        if ($Script:Reporting['MailboxProblems']['Data'] -is [System.Collections.IDictionary]) {
            New-HTMLTabPanel -Theme forge {
                New-HTMLTab -Name "Duplicate Alias" {
                    New-HTMLSection -HeaderText 'Exchange Online - Duplicate Alias' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['DuplicateAlias'] -Filtering
                    }
                    New-HTMLSection -HeaderText 'Exchange On-Premises - Duplicate Alias' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['DuplicateAlias'] -Filtering
                    }
                    New-HTMLSection -HeaderText 'Exchange - Duplicate Alias (Together)' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Both']['DuplicateAlias'] -Filtering
                    }
                }
                New-HTMLTab -Name "Duplicate Account" {
                    New-HTMLSection -HeaderText 'Exchange - Duplicate Account' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Both']['DuplicateAccount'] -Filtering
                    }
                }
                New-HTMLTab -Name 'No Database' {
                    New-HTMLSection -HeaderText 'Exchange On-Premises - No Database' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['NoDatabase'] -Filtering
                    }
                }
                New-HTMLTab -Name 'Inconsistent Data' {
                    New-HTMLSection -HeaderText 'Exchange Online - Broken DisplayName' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['BrokenDisplayName'] -Filtering
                    }
                    New-HTMLSection -HeaderText 'Exchange On-Premises - Broken DisplayName' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['BrokenDisplayName'] -Filtering
                    }
                    New-HTMLSection -HeaderText 'Exchange On-Premises - Missing UserPrincipalName' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['MissingUserPrincipalName'] -Filtering
                    }
                }
                New-HTMLTab -Name 'Contact Problems' {
                    New-HTMLSection -HeaderText 'Exchange Online - Contact Missing PrimarySmtp' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['ContactMissingPrimarySmtp'] -Filtering
                    }
                    New-HTMLSection -HeaderText 'Exchange On-Premises - Contact Missing PrimarySmtp' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['ContactMissingPrimarySmtp'] -Filtering
                    }
                    New-HTMLSection -HeaderText 'Exchange Online - Contact Missing ExternalEmail' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['ContactMissingExternalEmail'] -Filtering
                    }
                    New-HTMLSection -HeaderText 'Exchange On-Premises - Contact Missing ExternalEmail' {
                        New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['ContactMissingExternalEmail'] -Filtering
                    }
                }
            }
        }
    }
}
function ConvertTo-ExchangeAccessRights {
    [CmdletBinding()]
    Param (
        [parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][PSObject] $Object,
        [string] $Identity,
        [string] $AccessRights = 'FullAccess'
    )
    Process {
        [PSCustomObject]@{
            Identity          = $Identity
            Trustee           = $Object.PrimarySmtpAddress
            AccessControlType = 'Allow'
            AccessRights      = @($AccessRights)
            IsInherited       = $false
            InheritanceType   = 'None'
        }
    }
}
function Get-ExchangeMailboxPermission {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $ReversedPermissions,
        [System.Collections.IDictionary] $CacheType,
        $Mailbox,
        [switch] $Local,
        [switch] $ExpandGroupMembership
    )

    if ($Local) {
        $Permissions = Get-LocalMailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false
    }
    else {
        $Permissions = Get-MailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false
    }

    foreach ($Permission in $Permissions) {
        if ($Permission.Deny -eq $false) {
            if ($Permission.User -ne 'NT AUTHORITY\SELF') {
                if ($Local) {
                    # Temporary without Expanding Groups
                    if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                        $UserSplit = $Permission.User.Split("\")
                        $CurrentUser = $UserSplit[1]
                    }
                    elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') {
                        $CurrentUser = $CacheNames[$Permission.User]
                    }
                    if ($CurrentUser) {
                        foreach ($Right in $Permission.AccessRights) {
                            if ($Right -like 'FullAccess*') {
                                if (-not $ReversedPermissions[$CurrentUser]) {
                                    $ReversedPermissions[$CurrentUser] = [ordered] @{
                                        FullAccess   = [System.Collections.Generic.List[string]]::new()
                                        SendAs       = [System.Collections.Generic.List[string]]::new()
                                        SendOnBehalf = [System.Collections.Generic.List[string]]::new()
                                    }
                                }
                                $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias)
                            }
                        }
                    }
                }
                else {
                    foreach ($Right in $Permission.AccessRights) {
                        if ($Right -like 'FullAccess*') {
                            if ($Permission.User -like "*@*") {
                                if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                                    $UserSplit = $Permission.User.Split("\")
                                    $CurrentUser = $UserSplit[1]
                                }
                                elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') {
                                    $CurrentUser = $CacheNames[$Permission.User]
                                }
                                if ($CurrentUser) {
                                    if (-not $ReversedPermissions[$CurrentUser]) {
                                        $ReversedPermissions[$CurrentUser] = [ordered] @{
                                            FullAccess   = [System.Collections.Generic.List[string]]::new()
                                            SendAs       = [System.Collections.Generic.List[string]]::new()
                                            SendOnBehalf = [System.Collections.Generic.List[string]]::new()
                                        }
                                    }
                                    $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias)
                                }
                            }
                            else {
                                if ($ExpandGroupMembership) {
                                    if ($Permission.User) {
                                        $GroupMembers = Get-MyMailboxMembers -Identity $Permission.User -ErrorAction SilentlyContinue -Verbose:$false -Local:$Local.IsPresent | ConvertTo-ExchangeAccessRights -AccessRights 'FullAccess' -Identity $Permission.Identity
                                        foreach ($Member in $GroupMembers) {
                                            if ($Member.Trustee) {
                                                $CurrentUser = $CacheNames[$Member.Trustee]
                                                if ($CurrentUser) {
                                                    if (-not $ReversedPermissions[$CurrentUser]) {
                                                        $ReversedPermissions[$CurrentUser] = [ordered] @{
                                                            FullAccess   = [System.Collections.Generic.List[string]]::new()
                                                            SendAs       = [System.Collections.Generic.List[string]]::new()
                                                            SendOnBehalf = [System.Collections.Generic.List[string]]::new()
                                                        }
                                                    }
                                                    $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias)
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
function Get-MyMailboxSendAs {
    <#
    .SYNOPSIS
    Short function that returns Send-As permissions for mailbox.
 
    .DESCRIPTION
    Function that returns Send-As permissions for mailbox. It's replacement of Get-ADPermission cmdlet that is very slow and inefficient.
 
    .PARAMETER ADUser
    Active Directory user object
 
    .PARAMETER Identity
    DistinguishedName of mailbox
 
    .EXAMPLE
    Get-ADUser -Identity 'przemyslaw.klys' -Properties NtsecurityDescriptor | Get-MyMailboxSendAs
 
    .EXAMPLE
    $Mailbox = Get-Mailbox -Identity 'przemyslaw.klys'
    $Test = Get-MyMailboxSendAs -Identity $Mailbox.DistinguishedName
    $Test | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory, ParameterSetName = 'ADUser', ValueFromPipeline, ValueFromPipelineByPropertyName)][Microsoft.ActiveDirectory.Management.ADAccount] $ADUser,
        [parameter(Mandatory, ParameterSetName = 'Identity', ValueFromPipeline, ValueFromPipelineByPropertyName )][string] $Identity
    )
    process {
        if ($ADUser) {
            if (-not $ADUser.NtsecurityDescriptor) {
                Write-Warning -Message "Get-MyMailboxSendAs - Identity '$($ADUser.SamAccountName)' does not have ntSecurityDescriptor attribute. Please provide one or use Identity parameter."
                return
            }
        }
        else {
            if (-not $Script:ForestInformation) {
                $Script:ForestInformation = Get-WinADForestDetails
            }
            $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $Identity -ToDomainCN
            $QueryServer = $Script:ForestInformation['QueryServers'][$DomainName].HostName[0]
            $ADUser = Get-ADUser -Identity $Identity -Properties ntSecurityDescriptor -Server $QueryServer
        }
        $ExtendedPermissions = foreach ($Permission in $ADUser.NtsecurityDescriptor.Access) {
            # filter out extended right and object type (Send-As)
            if ($Permission.ActiveDirectoryRights -eq 'ExtendedRight' -and $Permission.objectType -eq "ab721a54-1e2f-11d0-9819-00aa0040529b") {
                [PSCustomObject] @{
                    User         = $Permission.IdentityReference
                    Identity     = if ($ADUser.CanonicalName) {
                        $ADUser.CanonicalName 
                    }
                    else {
                        $ADUser.DistinguishedName 
                    }
                    AccessRights = 'Send-As'
                    Deny         = $Permission.AccessControlType -ne 'Allow'
                    IsInherited  = $Permission.IsInherited
                }
            }
        }
        $ExtendedPermissions
    }
}
function New-HTMLReportExchangeEssentials {
    [cmdletBinding()]
    param(
        [Array] $Type,
        [switch] $Online,
        [switch] $HideHTML,
        [string] $FilePath
    )

    New-HTML -Author 'PrzemysÅ‚aw KÅ‚ys' -TitleText 'ExchangeEssentials Report' {
        New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
        New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLPanelStyle -BorderRadius 0px
        New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin

        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 "ExchangeEssentials - $($Script:Reporting['Version'])" -Color Blue
                } -JustifyContent flex-end -Invisible
            }
        }

        if ($Type.Count -eq 1) {
            foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) {
                if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true) {
                    if ($Script:ExchangeEssentialsConfiguration[$T]['Summary']) {
                        $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Summary']
                    }
                    & $Script:ExchangeEssentialsConfiguration[$T]['Solution']
                }
            }
        }
        else {
            foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) {
                if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true) {
                    if ($Script:ExchangeEssentialsConfiguration[$T]['Summary']) {
                        $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Summary']
                    }
                    New-HTMLTab -Name $Script:ExchangeEssentialsConfiguration[$T]['Name'] {
                        & $Script:ExchangeEssentialsConfiguration[$T]['Solution']
                    }
                }
            }
        }
    } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath
}
function New-HTMLReportExchangeEssentialsWithSplit {
    [cmdletBinding()]
    param(
        [Array] $Type,
        [switch] $Online,
        [switch] $HideHTML,
        [string] $FilePath,
        [string] $CurrentReport
    )

    # Split reports into multiple files for easier viewing
    $DateName = $(Get-Date -f yyyy-MM-dd_HHmmss)
    $FileName = [io.path]::GetFileNameWithoutExtension($FilePath)
    $DirectoryName = [io.path]::GetDirectoryName($FilePath)

    foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) {
        if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true -and ((-not $CurrentReport) -or ($CurrentReport -and $CurrentReport -eq $T))) {
            $NewFileName = $FileName + '_' + $T + "_" + $DateName + '.html'
            $FilePath = [io.path]::Combine($DirectoryName, $NewFileName)

            New-HTML -Author 'PrzemysÅ‚aw KÅ‚ys' -TitleText "ExchangeEssentials $CurrentReport Report" {
                New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
                New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
                New-HTMLPanelStyle -BorderRadius 0px
                New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin

                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 "ExchangeEssentials - $($Script:Reporting['Version'])" -Color Blue
                        } -JustifyContent flex-end -Invisible
                    }
                }
                if ($Script:ExchangeEssentialsConfiguration[$T]['Summary']) {
                    $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Summary']
                }
                & $Script:ExchangeEssentialsConfiguration[$T]['Solution']
            } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath
        }
    }
}
function Reset-ExchangeEssentialsStatus {
    [cmdletBinding()]
    param(

    )
    if (-not $Script:DefaultTypes) {
        $Script:DefaultTypes = foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) {
            if ($Script:ExchangeEssentialsConfiguration[$T].Enabled) {
                $T
            }
        }
    }
    else {
        foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) {
            if ($Script:ExchangeEssentialsConfiguration[$T]) {
                $Script:ExchangeEssentialsConfiguration[$T]['Enabled'] = $false
            }
        }
        foreach ($T in $Script:DefaultTypes) {
            if ($Script:ExchangeEssentialsConfiguration[$T]) {
                $Script:ExchangeEssentialsConfiguration[$T]['Enabled'] = $true
            }
        }
    }
}
$Script:ExchangeEssentialsConfiguration = [ordered] @{
    Mailbox         = $Script:ReportMailbox
    MailboxProblems = $Script:ReportMailboxProblems
}
function Get-MyMailbox {
    [CmdletBinding()]
    param(
        [switch] $IncludeStatistics,
        [switch] $IncludeCAS,
        [switch] $IncludeMailUsers,
        [switch] $Local,
        [int] $LimitProcessing,
        [switch] $SkipPermissions,
        [switch] $ExpandGroupMembership
    )

    $ReversedPermissions = [ordered] @{}
    $CacheMailbox = [ordered] @{}
    $CacheCasMailbox = [ordered] @{}
    $CacheNames = [ordered] @{}
    #$FinalOutput = [ordered] @{}
    $CacheType = [ordered] @{}
    $CacheTypeMailUser = [ordered] @{}
    $CacheExternalEmails = [ordered] @{}
    $CacheContacts = [ordered] @{}
    $CacheContactsLocal = [ordered] @{}
    $CacheRemoteDomains = [ordered] @{}
    $CacheRecipientPermissions = [ordered] @{}

    Write-Verbose -Message 'Get-MyMailbox - Getting Mailboxes'
    $Mailboxes = @(
        if ($Local) {
            $TimeLog = Start-TimeLog
            Write-Verbose -Message 'Get-MyMailbox - Getting Mailboxes (Local)'
            try {
                $LocalMailboxes = Get-LocalMailbox -ResultSize unlimited -ErrorAction Stop -Verbose:$false
                $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner
                Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Local) took $($EndTimeLog)"
            }
            catch {
                $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner
                Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Local) took $($EndTimeLog)"
                Write-Warning -Message "Get-MyMailbox - Unable to get Mailboxes. Error: $($_.Exception.Message.Replace("`r`n", " "))"
                return
            }
            foreach ($Mailbox in $LocalMailboxes) {
                $CacheNames[$Mailbox.UserPrincipalName] = $Mailbox.Alias
                $CacheNames[$Mailbox.Identity] = $Mailbox.Alias
                $CacheNames[$Mailbox.Alias] = $Mailbox.Alias
                $CacheType[$Mailbox.Alias] = 'On-Premises Mailbox'
                if ($Mailbox.PrimarySmtpAddress) {
                    $CacheExternalEmails[$Mailbox.PrimarySmtpAddress] = $Mailbox.Alias
                }
                $Mailbox
            }
        }
        Write-Verbose -Message 'Get-MyMailbox - Getting Mailboxes (Online)'
        $TimeLog = Start-TimeLog
        try {
            $getEXOMailboxSplat = @{
                Properties  = 'GrantSendOnBehalfTo', 'ForwardingSmtpAddress', 'RecipientTypeDetails', 'SamAccountName', 'WhenCreated', 'WhenMailboxCreated', 'HiddenFromAddressListsEnabled', 'ForwardingAddress', 'AccountDisabled'
                ResultSize  = 'unlimited'
                ErrorAction = 'Stop'
            }
            if ($Identity) {
                $getEXOMailboxSplat.Identity = $Identity
            }

            $OnlineMailboxes = Get-EXOMailbox @getEXOMailboxSplat -Verbose:$false
            $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner
            Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Online) took $($EndTimeLog) seconds"
        }
        catch {
            $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner
            Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Online) took $($EndTimeLog) seconds"
            Write-Warning -Message "Get-MyMailbox - Unable to get Mailboxes. Error: $($_.Exception.Message.Replace("`r`n", " "))"
            return
        }
        foreach ($Mailbox in $OnlineMailboxes) {
            $CacheNames[$Mailbox.UserPrincipalName] = $Mailbox.Alias
            $CacheNames[$Mailbox.Identity] = $Mailbox.Alias
            $CacheNames[$Mailbox.Alias] = $Mailbox.Alias
            $CacheType[$Mailbox.Alias] = 'Online Mailbox'
            $Mailbox
        }
        if ($IncludeMailUsers -and -not $Identity) {
            Write-Verbose -Message 'Get-MyMailbox - Getting MailUsers (Online)'
            $TimeLog = Start-TimeLog
            try {
                $getMailUserSplat = @{
                    ResultSize  = 'unlimited'
                    ErrorAction = 'Stop'
                }
                #if ($Identity) {
                # $getMailUserSplat.Identity = $Identity
                #}
                $MailUsersOnline = Get-MailUser @getMailUserSplat
                foreach ($M in $MailUsersOnline) {
                    if ($M.WindowsEmailAddress -or $M.PrimarySmtpAddress) {
                        if (-not $CacheExternalEmails[$M.WindowsEmailAddress] -or -not $CacheExternalEmails[$M.PrimarySmtpAddress]) {
                            if (-not $CacheExternalEmails[$M.WindowsEmailAddress]) {
                                $CacheExternalEmails[$M.WindowsEmailAddress] = $M.Alias
                            }
                            if (-not $CacheExternalEmails[$M.PrimarySmtpAddress]) {
                                $CacheExternalEmails[$M.PrimarySmtpAddress] = $M.Alias
                            }
                            $CacheNames[$M.PrimarySmtpAddress] = $M.Alias
                            $CacheNames[$M.UserPrincipalName] = $M.Alias
                            $CacheNames[$M.Identity] = $M.Alias
                            $CacheNames[$M.Alias] = $M.Alias
                            $CacheTypeMailUser[$M.Alias] = 'MailUser'
                            #$CacheType[$M.Alias] = 'MailUser'
                            $M
                        }
                    }
                }
            }
            catch {
                Write-Warning -Message "Get-MyMailbox - Unable to get MailUsers. Error: $($_.Exception.Message.Replace("`r`n", " "))"
                return
            }
            $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner
            Write-Verbose -Message "Get-MyMailbox - Getting MailUsers (Online) took $($EndTimeLog) seconds"
        }
    )

    if (-not $SkipPermissions) {
        Write-Verbose -Message 'Get-MyMailbox - Getting RecipientPermission'
        try {
            $RecipientPermissions = Get-EXORecipientPermission -ResultSize Unlimited -ErrorAction Stop -Verbose:$false
        }
        catch {
            Write-Warning -Message "Get-MyMailbox - Unable to get Recipient Permissions. Error: $($_.Exception.Message.Replace("`r`n", " "))"
            return
        }
    }
    if ($Local) {
        Write-Verbose -Message 'Get-MyMailbox - Getting Local Mail Contacts'
        try {
            [Array] $ContactsLocal = Get-LocalMailContact -ResultSize Unlimited -ErrorAction Stop -Verbose:$false
        }
        catch {
            Write-Warning -Message "Get-MyMailbox - Unable to get Local Mail Contacts. Error: $($_.Exception.Message.Replace("`r`n", " "))"
        }
    }
    Write-Verbose -Message 'Get-MyMailbox - Getting Mail Contacts'
    try {
        [Array] $Contacts = Get-MailContact -ResultSize Unlimited -ErrorAction Stop -Verbose:$false
    }
    catch {
        Write-Warning -Message "Get-MyMailbox - Unable to get Mail Contacts. Error: $($_.Exception.Message.Replace("`r`n", " "))"
        return
    }

    try {
        $RemoteDomains = @(
            Write-Verbose -Message 'Get-MyMailbox - Getting Remote Domains'
            Get-RemoteDomain -ResultSize Unlimited -ErrorAction Stop -Verbose:$false
            if ($Local) {
                Write-Verbose -Message 'Get-MyMailbox - Getting Local Remote Domains'
                Get-LocalRemoteDomain -ErrorAction Stop -Verbose:$false
            }
        )
    }
    catch {
        Write-Warning -Message "Get-MyMailbox - Unable to get Remote Domains. Error: $($_.Exception.Message.Replace("`r`n", " "))"
        return
    }

    Write-Verbose -Message 'Get-MyMailbox - Caching Objects'
    foreach ($C in $Contacts) {
        $CacheContacts[$C.Identity] = $C
    }
    foreach ($C in $ContactsLocal) {
        $CacheContactsLocal[$C.Identity] = $C
    }
    foreach ($Domain in $RemoteDomains) {
        $CacheRemoteDomains[$Domain.DomainName] = $Domain
    }
    foreach ($RecipientPermission in $RecipientPermissions) {
        if ($RecipientPermission.Trustee -ne 'NT AUTHORITY\SELF') {
            if (-not $CacheRecipientPermissions[$RecipientPermission.Identity]) {
                $CacheRecipientPermissions[$RecipientPermission.Identity] = [System.Collections.Generic.List[PSCustomobject]]::new()
            }
            if ($ExpandGroupMembership) {
                if ($RecipientPermission.Trustee -like "*@*") {
                    $CacheRecipientPermissions[$RecipientPermission.Identity].Add($RecipientPermission)
                }
                else {
                    if ($RecipientPermission.Trustee) {
                        $GroupMembers = Get-MyMailboxMembers -Identity $RecipientPermission.Trustee -ErrorAction SilentlyContinue -Verbose:$false -Local:$false | ConvertTo-ExchangeAccessRights -AccessRights SendAs -Identity $RecipientPermission.Identity
                        if ($GroupMembers) {
                            foreach ($Member in $GroupMembers) {
                                #Write-Verbose -Message "Get-MyMailbox - Expanding group and adding Member $($Member.Trustee) to $($RecipientPermission.Identity)"
                                $CacheRecipientPermissions[$RecipientPermission.Identity].Add($Member)
                            }
                        }
                        else {
                            $CacheRecipientPermissions[$RecipientPermission.Identity].Add($RecipientPermission)
                        }
                    }
                }
            }
            else {
                $CacheRecipientPermissions[$RecipientPermission.Identity].Add($RecipientPermission)
            }
        }
    }
    if ($IncludeCAS) {
        Write-Verbose -Message 'Get-MyMailbox - Getting CAS Mailboxes'
        try {
            if (-not $CasMailboxes) {
                $CasMailboxes = @(
                    Write-Verbose -Message 'Get-MyMailbox - Getting CAS Mailboxes (Online)'
                    Get-CasMailbox -ResultSize unlimited -ErrorAction Stop -Verbose:$false
                    if ($Local) {
                        Write-Verbose -Message 'Get-MyMailbox - Getting CAS Mailboxes (Local)'
                        Get-LocalCasMailbox -ResultSize unlimited -ErrorAction Stop -Verbose:$false
                    }
                )
            }
        }
        catch {
            Write-Warning -Message "Get-MyMailbox - Unable to get CasMailboxes. Error: $($_.Exception.Message.Replace("`r`n", " "))"
            return
        }
        foreach ($Mailbox in $CasMailboxes) {
            $CacheCasMailbox[$Mailbox.Identity] = $Mailbox
        }
    }
    $Count = 0

    $FilterdMailboxes = @(
        if ($LimitProcessing) {
            $LocalMailboxes | Select-Object -First $LimitProcessing
            $OnlineMailboxes | Select-Object -First $LimitProcessing
            if ($IncludeMailUsers) {
                $MailUsersOnline | Select-Object -First $LimitProcessing
            }
        }
        else {
            $Mailboxes
        }
    )
    if (-not $SkipPermissions) {
        Write-Verbose -Message 'Get-MyMailbox - Getting Permissions'
        foreach ($Mailbox in $FilterdMailboxes) {
            $Count++
            Write-Verbose -Message "Processing Mailbox $Count/$($Mailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName)"
            $TimeLog = Start-TimeLog
            $CacheMailbox[$Mailbox.Alias] = [ordered] @{
                Mailbox = $Mailbox
            }
            #$TimeLogPermissions = Start-TimeLog
            if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                try {
                    Write-Verbose -Message "Get-MyMailbox - Getting MailboxPermissions for $($Mailbox.Alias) - Local"
                    #$CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-LocalMailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false
                    $CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-ExchangeMailboxPermission -Local -ReversedPermissions $ReversedPermissions -CacheType $CacheType -Mailbox $Mailbox -ExpandGroupMembership:$ExpandGroupMembership.IsPresent
                }
                catch {
                    Write-Warning -Message "Get-MyMailbox - Unable to get MailboxPermissions for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))"
                }
            }
            elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') {
                try {
                    Write-Verbose -Message "Get-MyMailbox - Getting MailboxPermissions for $($Mailbox.Alias) - Online"
                    #$CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-MailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false
                    $CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-ExchangeMailboxPermission -ReversedPermissions $ReversedPermissions -CacheType $CacheType -Mailbox $Mailbox -ExpandGroupMembership:$ExpandGroupMembership.IsPresent
                }
                catch {
                    Write-Warning -Message "Get-MyMailbox - Unable to get MailboxPermissions for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))"
                }
            }
            if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                try {
                    Write-Verbose -Message "Get-MyMailbox - Getting MailboxADPermissions for $($Mailbox.Alias) - Local"
                    $CacheMailbox[$Mailbox.Alias].MailboxRecipientPermissions = Get-MyMailboxSendAs -Identity $Mailbox.DistinguishedName #Get-LocalADPermission -Identity $Mailbox.Identity -ErrorAction Stop
                }
                catch {
                    Write-Warning -Message "Get-MyMailbox - Unable to get MailboxADPermissions for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))"
                }
            }
            elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') {
                if ($CacheRecipientPermissions[$Mailbox.Identity] -and $CacheRecipientPermissions[$Mailbox.Identity].Count -gt 0) {
                    $CacheMailbox[$Mailbox.Alias].MailboxRecipientPermissions = $CacheRecipientPermissions[$Mailbox.Identity]
                }
            }
            if ($IncludeStatistics) {
                if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                    $CacheMailbox[$Mailbox.Alias]['Statistics'] = Get-LocalMailboxStatistics -Identity $Mailbox.Alias
                }
                elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') {
                    $CacheMailbox[$Mailbox.Alias]['Statistics'] = Get-MailboxStatistics -Identity $Mailbox.Alias
                }
                if ($Mailbox.ArchiveDatabase) {
                    try {
                        if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                            $Archive = Get-LocalMailboxStatistics -Identity ($Mailbox.Guid).ToString() -Archive -Verbose:$false -ErrorAction Stop
                        }
                        elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') {
                            $Archive = Get-MailboxStatistics -Identity ($Mailbox.Guid).ToString() -Archive -Verbose:$false -ErrorAction Stop
                        }
                        $CacheMailbox[$Mailbox.Alias]['StatisticsArchive'] = $Archive
                    }
                    catch {
                        Write-Warning -Message "Get-MyMailbox - Unable to get ArchiveStatistics for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))"
                    }
                }
            }
            <#
            foreach ($Permission in $CacheMailbox[$Mailbox.Alias].MailboxPermissions) {
                if ($Permission.Deny -eq $false) {
                    if ($Permission.User -ne 'NT AUTHORITY\SELF') {
                        if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                            $UserSplit = $Permission.User.Split("\")
                            $CurrentUser = $UserSplit[1]
                        } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') {
                            $CurrentUser = $CacheNames[$Permission.User]
                        }
                        if ($CurrentUser) {
                            foreach ($Right in $Permission.AccessRights) {
                                if ($Right -like 'FullAccess*') {
                                    if (-not $ReversedPermissions[$CurrentUser]) {
                                        $ReversedPermissions[$CurrentUser] = [ordered] @{
                                            FullAccess = [System.Collections.Generic.List[string]]::new()
                                            SendAs = [System.Collections.Generic.List[string]]::new()
                                            SendOnBehalf = [System.Collections.Generic.List[string]]::new()
                                        }
                                    }
                                    $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias)
                                }
                            }
                        } else {
                            # Write-Warning -Message "Unable to process $($Permission.User) for $($Mailbox.Alias)"
                        }
                    }
                }
            }
            #>

            # Process SendAs permissions into ReversedPermissions
            foreach ($Permission in $CacheMailbox[$Mailbox.Alias].MailboxRecipientPermissions) {
                if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') {
                    if ($Permission.Deny -eq $false -and $Permission.Inherited -eq $false) {
                        if ($Permission.User -ne 'NT AUTHORITY\SELF') {
                            $UserSplit = $Permission.User.Split("\")
                            $CurrentUser = $UserSplit[1]
                            if ($CurrentUser) {
                                foreach ($Right in $Permission.AccessRights) {
                                    if (($Right -like 'Send*')) {
                                        if (-not $ReversedPermissions[$CurrentUser]) {
                                            $ReversedPermissions[$CurrentUser] = [ordered] @{
                                                FullAccess   = [System.Collections.Generic.List[string]]::new()
                                                SendAs       = [System.Collections.Generic.List[string]]::new()
                                                SendOnBehalf = [System.Collections.Generic.List[string]]::new()
                                            }
                                        }
                                        $ReversedPermissions[$CurrentUser].SendAs.Add($Mailbox.Alias)
                                    }
                                }
                            }
                            else {
                                #Write-Warning -Message "Unable to process $($Permission.Trustee) for $($Mailbox.Alias)"
                            }
                        }
                    }
                }
                else {
                    if ($Permission.AccessControlType -eq 'Allow') {
                        if ($Permission.Trustee -ne 'NT AUTHORITY\SELF') {
                            $CurrentUser = $CacheNames[$Permission.Trustee]
                            if ($CurrentUser) {
                                foreach ($Right in $Permission.AccessRights) {
                                    if (($Right -like 'Send*')) {
                                        if (-not $ReversedPermissions[$CurrentUser]) {
                                            $ReversedPermissions[$CurrentUser] = [ordered] @{
                                                FullAccess   = [System.Collections.Generic.List[string]]::new()
                                                SendAs       = [System.Collections.Generic.List[string]]::new()
                                                SendOnBehalf = [System.Collections.Generic.List[string]]::new()
                                            }
                                        }
                                        $ReversedPermissions[$CurrentUser].SendAs.Add($Mailbox.Alias)
                                    }
                                }
                            }
                            else {
                                #Write-Warning -Message "Unable to process $($Permission.Trustee) for $($Mailbox.Alias)"
                            }
                        }
                    }
                }
            }
            # Process SendOnBehalf permissions into ReversedPermissions
            foreach ($Permission in $CacheMailbox[$Mailbox.Alias].Mailbox.GrantSendOnBehalfTo) {
                $CurrentUser = $CacheNames[$Permission]
                if ($CurrentUser) {
                    if (-not $ReversedPermissions[$CurrentUser]) {
                        $ReversedPermissions[$CurrentUser] = [ordered] @{
                            FullAccess   = [System.Collections.Generic.List[string]]::new()
                            SendAs       = [System.Collections.Generic.List[string]]::new()
                            SendOnBehalf = [System.Collections.Generic.List[string]]::new()
                        }
                    }

                    $ReversedPermissions[$CurrentUser].SendOnBehalf.Add($Mailbox.Alias)
                }
                else {
                    # Write-Warning -Message "Unable to process $($Permission) for $($Mailbox.Alias)"
                }
            }
            #$TimeLogProcessingEnd = Stop-TimeLog -Time $TimeLogProcessing
            $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner
            #Write-Verbose -Message "Processing Mailbox $Count/$($Mailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName) - [$TimeLogPermissionsEnd][$TimeLogRecipientEnd][$TimeLogStatsEnd][$TimeLogProcessingEnd]"
            Write-Verbose -Message "Processing Mailbox $Count/$($Mailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName) - [$EndTimeLog]"
        }
    }
    Write-Verbose -Message 'Get-MyMailbox - Reversing Permissions'
    foreach ($Alias in $ReversedPermissions.Keys) {
        if ($CacheMailbox[$Alias]) {
            $CacheMailbox[$Alias].FullAccess = $ReversedPermissions[$Alias].FullAccess
            $CacheMailbox[$Alias].SendAs = $ReversedPermissions[$Alias].SendAs
            $CacheMailbox[$Alias].SendOnBehalf = $ReversedPermissions[$Alias].SendOnBehalf
        }
    }
    Write-Verbose -Message 'Get-MyMailbox - Processing Mailboxes'
    $Count = 0
    foreach ($Mailbox in $FilterdMailboxes) {
        $Count++
        Write-Verbose -Message "Processing Mailbox $Count/$($FilterdMailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName)"
        # lets reset the variables
        $ForwardAddress = $null
        $IsForward = $false
        $ForwardingType = $null
        $ForwardingStatus = $null
        # establish forwarding status
        if ($Mailbox.RecipientType -eq 'MailUser') {
            if ($Mailbox.ExternalEmailAddress) {
                # the assumption is that the ExternalEmailAddress is the forwarding address, when there are more than 1 email addresses on mailuser
                $Counter = 0
                foreach ($Email in $Mailbox.EmailAddresses) {
                    if ($Email.StartsWith('smtp:', $true, $null)) {
                        $Counter++
                    }
                }
                if ($Counter -gt 1) {
                    $ForwardAddress = Convert-ExchangeEmail -Emails $Mailbox.ExternalEmailAddress -RemovePrefix
                    if ($ForwardAddress) {
                        $IsForward = $true
                        $ForwardingType = 'MailUserForward'
                        $ForwardingStatus = 'External'
                    }
                }
            }
            if (-not $ForwardAddress) {
                $ForwardAddress = $null
                $IsForward = $false
                $ForwardingType = 'None'
                $ForwardingStatus = 'None'
            }
        }
        else {
            if ($Mailbox.ForwardingAddress) {
                $Contact = $CacheContacts[$Mailbox.ForwardingAddress]
                $ContactLocal = $CacheContactsLocal[$Mailbox.ForwardingAddress]
                if ($Contact) {
                    $ForwardAddress = Convert-ExchangeEmail -Emails $Contact.ExternalEmailAddress -RemovePrefix
                    if ($ForwardAddress) {
                        $IsForward = $true
                    }
                    else {
                        $IsForward = $false
                    }
                }
                elseif ($ContactLocal) {
                    $ForwardAddress = Convert-ExchangeEmail -Emails $ContactLocal.ExternalEmailAddress -RemovePrefix
                    if ($ForwardAddress) {
                        $IsForward = $true
                    }
                    else {
                        $IsForward = $false
                    }
                }
                else {
                    $ForwardAddress = $Mailbox.ForwardingAddress
                    $IsForward = $true
                }
                $ForwardingType = 'ContactForward'
            }
            elseif ($Mailbox.ForwardingSmtpAddress) {
                $ForwardAddress = Convert-ExchangeEmail -Emails $Mailbox.ForwardingSmtpAddress -RemovePrefix
                if ($ForwardAddress) {
                    $IsForward = $true
                }
                else {
                    # this shouldn't happen
                    $IsForward = 'Unknown'
                }
                $ForwardingType = 'MailboxForward'
            }
            else {
                $ForwardAddress = $null
                $IsForward = $false
                $ForwardingType = 'None'
            }
        }
        if (-not $ForwardingStatus) {
            if ($ForwardAddress) {
                if ($ForwardAddress -like "*@*") {
                    $SplitAddress = $ForwardAddress -split "@"
                    $DomainName = $SplitAddress[1]
                    $DomainName = $DomainName.Trim()
                    if ($CacheRemoteDomains[$DomainName]) {
                        $ForwardingStatus = "Internal"
                    }
                    else {
                        $ForwardingStatus = "External"
                    }
                }
                else {
                    $ForwardingStatus = "Unknown"
                }
            }
            else {
                $ForwardingStatus = 'None'
            }
        }

        $Type = if ($CacheType[$Mailbox.Alias]) {
            $CacheType[$Mailbox.Alias]
        }
        elseif ($CacheTypeMailUser[$Mailbox.Alias]) {
            $CacheTypeMailUser[$Mailbox.Alias]
        }

        if (-not $SkipPermissions) {
            $User = [ordered] @{
                DisplayName                   = $Mailbox.DisplayName
                Alias                         = $Mailbox.Alias
                UserPrincipalName             = $Mailbox.UserPrincipalName
                Enabled                       = if ($Mailbox.AccountDisabled -eq $true) {
                    $false 
                }
                elseif ($Mailbox.AccountDisabled -eq $false) {
                    $true 
                }
                else {
                    $null 
                }
                Type                          = $Type
                TypeDetails                   = $Mailbox.RecipientTypeDetails
                PrimarySmtpAddress            = $Mailbox.PrimarySmtpAddress
                SamAccountName                = $Mailbox.SamAccountName
                ForwardingEnabled             = $IsForward
                ForwardingStatus              = $ForwardingStatus
                ForwardingType                = $ForwardingType
                ForwardingAddress             = $ForwardAddress

                FullAccess                    = $CacheMailbox[$Mailbox.Alias].FullAccess
                SendAs                        = $CacheMailbox[$Mailbox.Alias].SendAs
                SendOnBehalf                  = $CacheMailbox[$Mailbox.Alias].SendOnBehalf
                FullAccessCount               = $CacheMailbox[$Mailbox.Alias].FullAccess.Count
                SendAsCount                   = $CacheMailbox[$Mailbox.Alias].SendAs.Count
                SendOnBehalfCount             = $CacheMailbox[$Mailbox.Alias].SendOnBehalf.Count
                WhenCreated                   = $Mailbox.WhenCreated
                WhenMailboxCreated            = $Mailbox.WhenMailboxCreated
                HiddenFromAddressListsEnabled = $Mailbox.HiddenFromAddressListsEnabled
            }
        }
        else {
            $User = [ordered] @{
                DisplayName                   = $Mailbox.DisplayName
                Alias                         = $Mailbox.Alias
                UserPrincipalName             = $Mailbox.UserPrincipalName
                Enabled                       = if ($Mailbox.AccountDisabled -eq $true) {
                    $false 
                }
                elseif ($Mailbox.AccountDisabled -eq $false) {
                    $true 
                }
                else {
                    $null 
                }
                Type                          = $Type
                TypeDetails                   = $Mailbox.RecipientTypeDetails
                PrimarySmtpAddress            = $Mailbox.PrimarySmtpAddress
                SamAccountName                = $Mailbox.SamAccountName
                ForwardingEnabled             = $IsForward
                ForwardingStatus              = $ForwardingStatus
                ForwardingType                = $ForwardingType
                ForwardingAddress             = $ForwardAddress
                WhenCreated                   = $Mailbox.WhenCreated
                WhenMailboxCreated            = $Mailbox.WhenMailboxCreated
                HiddenFromAddressListsEnabled = $Mailbox.HiddenFromAddressListsEnabled
            }
        }
        if ($IncludeStatistics) {
            #$User['LastUserAccessTime'] = $CacheMailbox[$Mailbox.Alias].Statistics.LastUserAccessTime
            $User['LastLogonTime'] = $CacheMailbox[$Mailbox.Alias].Statistics.LastLogonTime
            $User['TotalItems'] = $CacheMailbox[$Mailbox.Alias].Statistics.ItemCount
            $User['TotalGB'] = Convert-ExchangeSize -Size $CacheMailbox[$Mailbox.Alias].Statistics.TotalItemSize -To GB
            $User['TotalArchiveItems'] = $CacheMailbox[$Mailbox.Alias].StatisticsArchive.ItemCount
            $User['TotalArchiveGB'] = Convert-ExchangeSize -Size $CacheMailbox[$Mailbox.Alias].StatisticsArchive.TotalItemSize -To GB
        }
        if ($IncludeCAS) {
            $CasProperties = @(
                'ActiveSyncEnabled'
                'OWAEnabled'
                'OWAforDevicesEnabled'
                'ECPEnabled'
                'PopEnabled'
                'PopMessageDeleteEnabled'
                'ImapEnabled'
                'MAPIEnabled'
                'MapiHttpEnabled'
                'UniversalOutlookEnabled'
                'OutlookMobileEnabled'
                'MacOutlookEnabled'
                'EwsEnabled'
                'OneWinNativeOutlookEnabled'
                'BulkMailEnabled'
                'SmtpClientAuthenticationDisabled'
            )
            if ($CacheCasMailbox[$Mailbox.Identity]) {
                foreach ($Property in $CasProperties) {
                    $User[$Property] = $CacheCasMailbox[$Mailbox.Alias].$Property
                }
            }
            else {
                foreach ($Property in $CasProperties) {
                    $User[$Property] = $null
                }
            }
        }
        [PSCustomObject] $User

        #$FinalOutput[$Mailbox.Alias] = $ConvertedUser
        #$ConvertedUser
    }
}
function Get-MyMailboxMembers {
    [alias('Get-ExchangeMembersRecursive', 'Get-MyMailboxMembersRecursive')]
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory, ValueFromPipeline)] $Identity,
        [switch] $Local,
        [Parameter(DontShow)][int] $Nesting = -1,
        [Parameter(DontShow)][switch] $Nested
    )
    Begin {
        $IgnoreAccounts = @(
            'Everyone'
            'NT AUTHORITY\Authenticated Users'
            "Discovery Management"
        )
        if ($Nesting -eq -1) {
            $GroupCache = [ordered] @{}
        }
    }
    Process {
        $Nesting++
        $MatchRegex = [Regex]::Matches($Identity, "S-\d-\d+-(\d+-|){1,14}\d+")
        if ($MatchRegex.Success) {
            # do nothing
        }
        elseif ($Identity -in $IgnoreAccounts) {
            # do nothing
        }
        else {
            if (-not $GroupCache[$Identity]) {
                $GroupCache[$Identity] = $true
            }
            else {
                Write-Verbose -Message "Get-MyMailboxMembers - Identity '$Identity' already processed. Circular reference detected."
                return
                # Write-Color "Identity '$Identity' already processed." -Color Red
                #return
            }
            Write-Verbose -Message "Processing '$Identity'"
            if ($Local) {
                $Members = try {
                    Get-LocalDistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop
                }
                catch {
                    try {
                        Get-LocalDynamicDistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop
                    }
                    catch {
                        Write-Warning -Message "Get-MyMailboxMembers - Identity '$Identity' is not a valid group."
                    }
                }
            }
            else {
                $Members = try {
                    Get-DistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop
                }
                catch {
                    try {
                        Get-DynamicDistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop
                    }
                    catch {
                        Write-Warning -Message "Get-MyMailboxMembers - Identity '$Identity' is not a valid group."
                    }
                }
            }
            foreach ($Member in $Members) {
                switch ($Member) {
                    { $_.RecipientType -notlike "*Group*" } {
                        $_
                    }
                    Default {
                        if ($_.primarysmtpAddress) {
                            Get-MyMailboxMembers -Identity $_.primarysmtpAddress -Local:$Local.IsPresent -Nested -Nesting $Nesting
                        }
                    }
                }
            }
        }
    }
    End {
        if (-not $Nested) {
            #Write-Color -Text "Get-MyMailboxMembers - Clearing GroupCache" -Color Red
            $GroupCache = $Null
        }
    }
}
function Get-MyMailboxProblems {
    [CmdletBinding()]
    param(
        [switch] $Local
    )
    $Problems = [ordered] @{
        Data   = [ordered] @{
            ContactsLocal   = $Null
            ContactsOnline  = $Null
            MailboxesLocal  = $Null
            MailboxesOnline = $Null
        }
        Online = [ordered] @{
            BrokenDisplayName           = [System.Collections.Generic.List[PSCustomObject]]::new()
            DuplicateAlias              = [System.Collections.Generic.List[PSCustomObject]]::new()
            NoDatabase                  = [System.Collections.Generic.List[PSCustomObject]]::new()
            ContactMissingPrimarySmtp   = [System.Collections.Generic.List[PSCustomObject]]::new()
            ContactMissingExternalEmail = [System.Collections.Generic.List[PSCustomObject]]::new()
            MissingUserPrincipalName    = [System.Collections.Generic.List[PSCustomObject]]::new()
        }
        Local  = [ordered] @{
            BrokenDisplayName           = [System.Collections.Generic.List[PSCustomObject]]::new()
            DuplicateAlias              = [System.Collections.Generic.List[PSCustomObject]]::new()
            NoDatabase                  = [System.Collections.Generic.List[PSCustomObject]]::new()
            ContactMissingPrimarySmtp   = [System.Collections.Generic.List[PSCustomObject]]::new()
            ContactMissingExternalEmail = [System.Collections.Generic.List[PSCustomObject]]::new()
            MissingUserPrincipalName    = [System.Collections.Generic.List[PSCustomObject]]::new()
        }
        Both   = [ordered] @{
            DuplicateAlias   = [System.Collections.Generic.List[PSCustomObject]]::new()
            DuplicateAccount = [System.Collections.Generic.List[PSCustomObject]]::new()
        }
    }

    $PropertiesMailbox = @(
        'UserPrincipalName', 'Alias', 'DisplayName', 'Database', 'PrimarySmtpAddress', 'RecipientType', 'RecipientTypeDetails', 'ExchangeUserAccountControl' #, 'Identity'
    )
    if ($Local) {
        try {
            $ContactsLocal = Get-LocalMailContact -ResultSize Unlimited -ErrorAction Stop
        }
        catch {
            Write-Warning "Get-MyMailboxProblems - Unable to get local contacts: $($_.Exception.Message)"
            $ContactsLocal = @()
        }
        try {
            $MailboxesLocal = Get-LocalMailbox -ResultSize Unlimited | Select-Object -Property $PropertiesMailbox -ErrorAction Stop
        }
        catch {
            Write-Warning "Get-MyMailboxProblems - Unable to get local mailboxes: $($_.Exception.Message)"
            $MailboxesLocal = @()
        }
    }
    try {
        $ContactsOnline = Get-LocalMailContact -ResultSize Unlimited -ErrorAction Stop
    }
    catch {
        Write-Warning "Get-MyMailboxProblems - Unable to get online contacts: $($_.Exception.Message)"
        $ContactsOnline = @()
    }
    try {
        $MailboxesOnline = Get-EXOMailbox -ResultSize Unlimited -Properties $PropertiesMailbox | Select-Object -Property $PropertiesMailbox -ErrorAction Stop
    }
    catch {
        Write-Warning "Get-MyMailboxProblems - Unable to get online mailboxes: $($_.Exception.Message)"
        $MailboxesOnline = @()
    }
    foreach ($C in $ContactsLocal) {
        if (-not $C.PrimarySmtpAddress) {
            $Problems.Local.ContactMissingPrimarySmtp.Add($C)
        }
        if (-not $C.ExternalEmailAddress) {
            $Problems.Local.ContactMissingExternalEmail.Add($C)
        }
    }
    foreach ($C in $ContactsOnline) {
        if (-not $C.PrimarySmtpAddress) {
            $Problems.Online.ContactMissingPrimarySmtp.Add($C)
        }
        if (-not $C.ExternalEmailAddress) {
            $Problems.Online.ContactMissingExternalEmail.Add($C)
        }
    }

    foreach ($M in $MailboxesLocal) {
        if ($M.DisplayName.StartsWith(' ') -or $M.DisplayName.EndsWith(' ')) {
            $Problems.Local.BrokenDisplayName.Add($M)
        }
        if ($Null -eq $M.Database) {
            $Problems.Local.NoDatabase.Add($M)
        }
        if ($null -eq $M.UserPrincipalName) {
            $Problems.Local.MissingUserPrincipalName.Add($M)
        }
    }
    foreach ($M in $MailboxesOnline) {
        if ($M.DisplayName.StartsWith(' ') -or $M.DisplayName.EndsWith(' ')) {
            $Problems.Online.BrokenDisplayName.Add($M)
        }
        if ($Null -eq $M.Database) {
            $Problems.Online.NoDatabase.Add($M)
        }
        if ($null -eq $M.UserPrincipalName) {
            $Problems.Local.MissingUserPrincipalName.Add($M)
        }
    }

    $CacheAll = [ordered] @{}
    $Cache = [ordered] @{}
    foreach ($M in $MailboxesOnline) {
        if (-not $Cache[$M.Alias]) {
            $Cache[$M.Alias] = $M
        }
        else {
            if (-not $CacheAll[$M.Alias]) {
                $CacheAll[$M.Alias] = [System.Collections.Generic.List[PSCustomObject]]::new()
                $CacheAll[$M.Alias].Add($Cache[$M.Alias])
            }
            $CacheAll[$M.Alias].Add($M)
        }
    }
    foreach ($M in $CacheAll.Values) {
        $Problems.Online.DuplicateAlias.Add($M)
    }
    # lets reset for local
    $CacheAll = [ordered] @{}
    $Cache = [ordered] @{}
    foreach ($M in $MailboxesLocal) {
        if (-not $Cache[$M.Alias]) {
            $Cache[$M.Alias] = $M
        }
        else {
            if (-not $CacheAll[$M.Alias]) {
                $CacheAll[$M.Alias] = [System.Collections.Generic.List[PSCustomObject]]::new()
                $CacheAll[$M.Alias].Add($Cache[$M.Alias])
            }
            $CacheAll[$M.Alias].Add($M)
        }
    }
    foreach ($M in $CacheAll.Values) {
        $Problems.Local.DuplicateAlias.Add($M)
    }
    # both?
    $CacheAll = [ordered] @{}
    $Cache = [ordered] @{}
    $CacheUPN = [ordered] @{}
    $CacheAllUPN = [ordered] @{}
    $Mailboxes = @(
        if ($MailboxesOnline.Count -gt 0) {
            $MailboxesOnline
        }
        if ($MailboxesLocal.Count -gt 0) {
            $MailboxesLocal
        }
    )
    foreach ($M in $Mailboxes) {
        # Duplicate Accounts
        if (-not $CacheUPN[$M.PrimarySmtpAddress]) {
            $CacheUPN[$M.PrimarySmtpAddress] = $M
        }
        else {
            if (-not $CacheAllUPN[$M.PrimarySmtpAddress]) {
                $CacheAllUPN[$M.PrimarySmtpAddress] = [System.Collections.Generic.List[PSCustomObject]]::new()
                $CacheAllUPN[$M.PrimarySmtpAddress].Add($CacheUPN[$M.PrimarySmtpAddress])
            }
            $CacheAllUPN[$M.PrimarySmtpAddress].Add($M)
        }
        # Duplicate Aliases
        if (-not $Cache[$M.Alias]) {
            $Cache[$M.Alias] = $M
        }
        else {
            if (-not $CacheAll[$M.Alias]) {
                $CacheAll[$M.Alias] = [System.Collections.Generic.List[PSCustomObject]]::new()
                $CacheAll[$M.Alias].Add($Cache[$M.Alias])
            }
            $CacheAll[$M.Alias].Add($M)
        }
    }
    foreach ($M in $CacheAll.Values) {
        $Problems.Both.DuplicateAlias.Add($M)
    }
    foreach ($M in $CacheAllUPN.Values) {
        $Problems.Both.DuplicateAccount.Add($M)
    }

    if ($Local) {
        $Problems.Data.ContactsLocal = $ContactsLocal
        $Problems.Data.MailboxesLocal = $MailboxesLocal
    }
    $Problems.Data.ContactsOnline = $ContactsOnline
    $Problems.Data.MailboxesOnline = $MailboxesOnline

    $Problems
}
function Invoke-ExchangeEssentials {
    [CmdletBinding()]
    param(
        [string] $FilePath,
        [Parameter(Position = 0)][string[]] $Type,
        [switch] $PassThru,
        [switch] $HideHTML,
        [switch] $Online,
        [switch] $SplitReports
    )
    $Script:Reporting = [ordered] @{}
    $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Invoke-ExchangeEssentials' -RepositoryOwner 'evotecit' -RepositoryName 'ExchangeEssentials'
    $Script:Reporting['Settings'] = @{
        ShowError   = $ShowError.IsPresent
        ShowWarning = $ShowWarning.IsPresent
        HideSteps   = $HideSteps.IsPresent
    }

    Write-Color '[i]', "[ExchangeEssentials] ", 'Version', ' [Informative] ', $Script:Reporting['Version'] -Color Yellow, DarkGray, Yellow, DarkGray, Magenta

    $Supported = [System.Collections.Generic.List[string]]::new()
    [Array] $NotSupported = foreach ($T in $Type) {
        if ($T -notin $Script:ExchangeEssentialsConfiguration.Keys ) {
            $T
        }
        else {
            $Supported.Add($T)
        }
    }
    if ($Supported) {
        Write-Color '[i]', "[ExchangeEssentials] ", 'Supported types', ' [Informative] ', "Chosen by user: ", ($Supported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
    }
    if ($NotSupported) {
        Write-Color '[i]', "[ExchangeEssentials] ", 'Not supported types', ' [Informative] ', "Following types are not supported: ", ($NotSupported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
        Write-Color '[i]', "[ExchangeEssentials] ", 'Not supported types', ' [Informative] ', "Please use one/multiple from the list: ", ($Script:ExchangeEssentialsConfiguration.Keys -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
        return
    }

    # Lets make sure we only enable those types which are requestd by user
    if ($Type) {
        foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) {
            $Script:ExchangeEssentialsConfiguration[$T].Enabled = $false
        }
        # Lets enable all requested ones
        foreach ($T in $Type) {
            $Script:ExchangeEssentialsConfiguration[$T].Enabled = $true
        }
    }

    # Build data
    foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) {
        if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true) {
            $Script:Reporting[$T] = [ordered] @{
                Name              = $Script:ExchangeEssentialsConfiguration[$T].Name
                ActionRequired    = $null
                Data              = $null
                Exclusions        = $null
                WarningsAndErrors = $null
                Time              = $null
                Summary           = $null
                Variables         = Copy-Dictionary -Dictionary $Script:ExchangeEssentialsConfiguration[$T]['Variables']
            }
            if ($Exclusions) {
                if ($Exclusions -is [scriptblock]) {
                    $Script:Reporting[$T]['ExclusionsCode'] = $Exclusions
                }
                if ($Exclusions -is [Array]) {
                    $Script:Reporting[$T]['Exclusions'] = $Exclusions
                }
            }

            $TimeLogExchangeEssentials = Start-TimeLog
            Write-Color -Text '[i]', '[Start] ', $($Script:ExchangeEssentialsConfiguration[$T]['Name']) -Color Yellow, DarkGray, Yellow
            $OutputCommand = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Execute'] -WarningVariable CommandWarnings -ErrorVariable CommandErrors -ArgumentList $Forest, $ExcludeDomains, $IncludeDomains
            if ($OutputCommand -is [System.Collections.IDictionary]) {
                # in some cases the return will be wrapped in Hashtable/orderedDictionary and we need to handle this without an array
                $Script:Reporting[$T]['Data'] = $OutputCommand
            }
            else {
                # since sometimes it can be 0 or 1 objects being returned we force it being an array
                $Script:Reporting[$T]['Data'] = [Array] $OutputCommand
            }
            Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Processing']
            $Script:Reporting[$T]['WarningsAndErrors'] = @(
                if ($ShowWarning) {
                    foreach ($War in $CommandWarnings) {
                        [PSCustomObject] @{
                            Type       = 'Warning'
                            Comment    = $War
                            Reason     = ''
                            TargetName = ''
                        }
                    }
                }
                if ($ShowError) {
                    foreach ($Err in $CommandErrors) {
                        [PSCustomObject] @{
                            Type       = 'Error'
                            Comment    = $Err
                            Reason     = $Err.CategoryInfo.Reason
                            TargetName = $Err.CategoryInfo.TargetName
                        }
                    }
                }
            )
            $TimeEndExchangeEssentials = Stop-TimeLog -Time $TimeLogExchangeEssentials -Option OneLiner
            $Script:Reporting[$T]['Time'] = $TimeEndExchangeEssentials
            Write-Color -Text '[i]', '[End ] ', $($Script:ExchangeEssentialsConfiguration[$T]['Name']), " [Time to execute: $TimeEndExchangeEssentials]" -Color Yellow, DarkGray, Yellow, DarkGray

            if ($SplitReports) {
                Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report for ', $T -Color Yellow, DarkGray, Yellow
                $TimeLogHTML = Start-TimeLog
                New-HTMLReportExchangeEssentialsWithSplit -FilePath $FilePath -Online:$Online -HideHTML:$HideHTML -CurrentReport $T
                $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner
                Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report for', $T, " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray
            }
        }
    }
    if ( -not $SplitReports) {
        Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report' -Color Yellow, DarkGray, Yellow
        $TimeLogHTML = Start-TimeLog
        if (-not $FilePath) {
            $FilePath = Get-FileName -Extension 'html' -Temporary
        }
        New-HTMLReportExchangeEssentials -Type $Type -Online:$Online.IsPresent -HideHTML:$HideHTML.IsPresent -FilePath $FilePath
        $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner
        Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report', " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray
    }
    Reset-ExchangeEssentialsStatus
    if ($PassThru) {
        $Script:Reporting
    }
}

[scriptblock] $SourcesAutoCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $Script:ExchangeEssentialsConfiguration.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" }
}

Register-ArgumentCompleter -CommandName Invoke-ExchangeEssentials -ParameterName Type -ScriptBlock $SourcesAutoCompleter

if ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release -lt 379893) {
    Write-Warning "This module requires .NET Framework 4.5.2 or later."; return 
} 

# Export functions and aliases as required
Export-ModuleMember -Function @('Get-MyMailbox', 'Get-MyMailboxMembers', 'Get-MyMailboxProblems', 'Invoke-ExchangeEssentials') -Alias @('Get-ExchangeMembersRecursive', 'Get-MyMailboxMembersRecursive')
# SIG # Begin signature block
# MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDGMyEKY2RemsvM
# XjtsB4fHB5H+I2MNPnlvAlzbOy7X26CCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/
# X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx
# MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX
# ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj
# aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7
# ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB
# uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu
# 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg
# LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG
# FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc
# ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh
# cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2
# 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD
# y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW
# BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O
# BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy
# NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT
# SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g
# qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s
# 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q
# BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4
# 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w
# QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z
# iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn
# LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza
# ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy
# 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA
# dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl
# G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU
# otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg
# Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw
# MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p
# a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD
# VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV
# OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE
# h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd
# GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0
# 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA
# o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw
# 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP
# 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi
# W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK
# RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA
# BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID
# AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD
# VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV
# HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw
# OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy
# MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0
# cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl
# U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG
# SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50
# ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa
# 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2
# CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0
# djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N
# 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi
# zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38
# wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y
# n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z
# n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe
# 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC
# BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS
# U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB
# ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G
# CSqGSIb3DQEJBDEiBCCJw+hPwhdc6Xy2EEvddT4rKeAFCwlCfaWfbd4/J7s7BTAN
# BgkqhkiG9w0BAQEFAASCAgBKdXldO2sU/Ai3N7mmx8U6P4dT8RDRCiYsuy5Aq++n
# MsgGNrYnxop6w+vCTJbESv4JcsjoUvawlXGlIb5HtGHB758KMQvF64JGvpND2Om/
# uqqupw1tc+s1YLZYT06KbJM9jH8YLPKKecsyyVHg5u9bGFHjd+MyxLw9d9CnO+1S
# GcJzFPqaZGHvLxSLrd/UAZvnCqQ7p8OqfQfXJeM+KqBnuaCc9zI6M7/RHdW8rhrX
# Bco5iKIPhfccFXaYXdMkP0Vhmu3mWEwz1SQbK5sSt4YEDXNB1uYFF1Q8MVF4MiyQ
# 7+s6EO5ae6bO0Cebh5uinpxcqpkJ+zbkn7I4rNNVQ31jVX29wXmCFqy0VxABeQx5
# 0R1G7k1gp6NDHsebpmc3HIEHyKS107lTYcLZVL+b58JtjDuy/OIpPjpL4iVaA8Q4
# t4ENL25IXAs420Om/Guz9qdP4QlKVxEglaIZ/B+pV805y2NlZkIRtsp4KstT46Ob
# XP2ysy2Y85MatglNZAHsviDuYmNqbCnGu/hHnS2eWKOc8vj0Kn7iW58uBYoOVTUc
# mLU5DJIgMv7MuUFVy1Zso82RPDrVHJAIYHGspl45wUuNOi15nP0Z2pnb92oYSvgW
# XgSC5mcDnGl+xTfNCXsaeyVnE9KXdlDpoQdwWGOV+GjYOHeQq4Rfrkfb/1G5zEez
# fqGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yMzEyMTIxMjI0MTBaMC8GCSqGSIb3DQEJBDEi
# BCCpIXnHToUUh2e3BC9di4Ig/JRVV3gbjm7NZEga/RZZUDANBgkqhkiG9w0BAQEF
# AASCAgCYkHFSbdskfz9Blr/Fzw/KyNzKJ1kJtVi0ET1uc7EpDkSXkpLhO+0i2zBP
# OwBpFfdItFH/Nk8pBbG4C7elHUpxrouwKOc0zwzS4Q8C2VFaBjIlAELh4P7zAQmf
# TrPljVJ2+yvu4dy0soSAiTAW2z/ZH1H/tuWavpw7sKzJDk8C7Sgru+c34quK85Jo
# McP0t8q2auTOjxLwbJL12VVdZXGevq6XOYDlx//9N5gmfNB80bnBzszftTV48jyr
# FvqmgYcQ5Trxjj26KGk2OVKF0NtCZuGDmRdtDPXYd4KvrJEygDaHcN5x2u4eLbDH
# nYLoqCabredQV2XYycb0LTLWZMgSyZMTbKwIJzew0O2zyq4CpTk/b0KDfKv5uUZj
# gcUdugUGp1QJyLaFpOFTkaULr+pOfSxhxwX4UB/F1EE1xWXLaa7bLpStDPwkOUxS
# zqelU35G1BBnHxXGXPgSV5+b6TaeCiett23EhyR8eUbkaI6jjOECFW1aI+/4yhQ1
# VhwyKCqup/sU83/hwzrHoyOSkAXAd2bQ0IkxBPAEmpIQ6pd+qggRPy1vffG6j73u
# VmUoeLumUhcpsfXY6PTphB02R/vVzcco2Ywqi76Fgvj7tJakuljXkVMSLpuvxUSv
# nePcBOQL1Qp27hge/P0yd0399qhFVIZj+6MDpSQkdxB6FR1J0A==
# SIG # End signature block