Bin/ADSyncDiagnostics/PSScripts/ADSyncPasswordHashSyncDiagnostics.ps1

#-------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
#-------------------------------------------------------------------------

#
# Event IDs
#
Set-Variable EventIdPwdSyncTroubleshootingRun               2001 -Option Constant -Scope script
Set-Variable EventIdSingleObjectDiagnosticsRun              2002 -Option Constant -Scope script
Set-Variable EventIdStagingModeEnabled                      2003 -option Constant -Scope script
Set-Variable EventIdConnectorNoHeartBeat                    2004 -Option Constant -Scope script
Set-variable EventIdDomainIsNotReachable                    2005 -Option Constant -Scope script
Set-Variable EventIdPwdSyncLocalConfigDisabled              2006 -Option Constant -Scope script
Set-Variable EventIdPwdSyncLocalAndCloudConfigDifferent     2007 -Option Constant -Scope script
Set-Variable EventIdConnectorAccountPwdSyncPermissionFailed 2008 -Option Constant -Scope script
Set-Variable EventIdPwdSyncLocalEnabledAndCloudDisabled     2010 -Option Constant -Scope script
Set-Variable EventIdPwdSyncLocalDisabledAndCloudEnabled     2011 -Option Constant -Scope script
Set-Variable EventIdPwdSyncCloudConfigNull                  2012 -Option Constant -Scope script

Set-Variable EventIdPersistentDomainFailure                 2013 -Option Constant -Scope script
Set-Variable EventIdTemporaryDomainFailure                  2014 -Option Constant -Scope script
Set-Variable EventIdCannotResolveDomainController           2015 -Option Constant -Scope script
Set-Variable EventIdCannotLocateDomainController            2016 -Option Constant -Scope script
Set-Variable EventIdCannotBindToDomainController            2017 -Option Constant -Scope script
Set-Variable EventIdPersistentRPCError                      2018 -Option Constant -Scope script
Set-Variable EventIdTemporaryRPCError                       2019 -Option Constant -Scope script
Set-Variable EventIdPersistentLDAPConnectionError           2020 -Option Constant -Scope script
Set-Variable EventIdTemporaryLDAPConnectionError            2021 -Option Constant -Scope script
Set-Variable EventIdPasswordSyncCryptographicException      2022 -Option Constant -Scope script
Set-Variable EventIdPasswordSyncInternalError               2023 -Option Constant -Scope script

# Password Hash Sync Batch AWS API Call Failures - Event IDs
Set-Variable EventIdPwdSyncBatchAWSFailure                                           2024 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailurePwdSyncCloudConfigNotActivated             2025 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureIdentitySyncCloudConfigNotActivated        2026 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureAADAccessDenied                            2027 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureEndpointNotFoundException                  2028 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureSecurityNegotiationException               2029 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureAdalServiceException                       2030 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureAdalServiceExUserRealmDiscoveryFailed      2031 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureAdalServiceExAccountMustBeAddedToTenant    2032 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureAdalServiceExOldPasswordUsed               2033 -Option Constant -Scope script   
Set-Variable EventIdPwdSyncBatchAWSFailureAdalServiceExAccountDisabled               2034 -Option Constant -Scope script
Set-Variable EventIdPwdSyncBatchAWSFailureAdalServiceExInvalidUsernameOrPwd          2035 -Option Constant -Scope script  
Set-Variable EventIdPwdSyncBatchAWSFailureAdalServiceExTenantNotFound                2036 -Option Constant -Scope script

# Password Hash Sync Ping AWS API Call Failures - Event IDs
Set-Variable EventIdPwdSyncPingAWSFailure                                            2037 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailurePwdSyncCloudConfigNotActivated              2038 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureIdentitySyncCloudConfigNotActivated         2039 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureAADAccessDenied                             2040 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureEndpointNotFoundException                   2041 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureSecurityNegotiationException                2042 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureAdalServiceException                        2043 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureAdalServiceExUserRealmDiscoveryFailed       2044 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureAdalServiceExAccountMustBeAddedToTenant     2045 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureAdalServiceExOldPasswordUsed                2046 -Option Constant -Scope script   
Set-Variable EventIdPwdSyncPingAWSFailureAdalServiceExAccountDisabled                2047 -Option Constant -Scope script
Set-Variable EventIdPwdSyncPingAWSFailureAdalServiceExInvalidUsernameOrPwd           2048 -Option Constant -Scope script  
Set-Variable EventIdPwdSyncPingAWSFailureAdalServiceExTenantNotFound                 2049 -Option Constant -Scope script

Set-Variable EventIdRestartPwdSyncChannelSuccess                                     2050 -Option Constant -Scope script
Set-Variable EventIdRestartPwdSyncChannelFailure                                     2051 -Option Constant -Scope script
Set-Variable EventIdPwdSyncActivityWithoutHeartbeat                                  2052 -Option Constant -Scope script

# Password Hash Sync Health Task Failure - Event IDs
Set-Variable EventIdPersistentHealthTaskFailure                                      2053 -Option Constant -Scope script
Set-Variable EventIdTemporaryHealthTaskFailure                                       2054 -Option Constant -Scope script

Set-Variable EventIdConnectorPwdSyncStopped                                          2055 -Option Constant -Scope script

Set-Variable EventIdIsPwdSyncGeneralDiagnosticsHelpful                               2056 -Option Constant -Scope script

Set-Variable EventIdSingleObjectConnectorDisabled             2201 -Option Constant -Scope script
Set-Variable EventIdSingleObjectDisconnector                  2202 -Option Constant -Scope script
Set-Variable EventIdSingleObjectNotSyncedToAADCS              2203 -Option Constant -Scope script
Set-Variable EventIdSingleObjectNoADPwdSyncRule               2204 -Option Constant -Scope script
Set-Variable EventIdSingleObjectNoAADPwdSyncRule              2205 -Option Constant -Scope script
Set-Variable EventIdSingleObjectNoPwdHistory                  2206 -Option Constant -Scope script
Set-Variable EventIdSingleObjectFilteredByTarget              2207 -Option Constant -Scope script
Set-Variable EventIdSingleObjectNotExported                   2208 -Option Constant -Scope script
Set-Variable EventIdSingleObjectNoTargetConnection            2209 -Option Constant -Scope script
Set-Variable EventIdSingleObjectSuccess                       2210 -Option Constant -Scope script
Set-Variable EventIdSingleObjectOtherFailure                  2211 -Option Constant -Scope script
Set-Variable EventIdIsPwdSyncSingleObjectDiagnosticsHelpful   2212 -Option Constant -Scope script

#
# Event Messages
#
Set-Variable EventMsgPwdSyncTroubleshootingRun                "Workflow has been run."                                                                                               -Option Constant -Scope script
Set-Variable EventMsgSingleObjectDiagnosticsRun               "Single object diagnostics has been run."                                                                              -Option Constant -Scope script
Set-Variable EventMsgStagingModeEnabled                       "Staging Mode is enabled."                                                                                             -Option Constant -Scope script
Set-Variable EventMsgConnectorNoHeartBeat                     "No Password Hash Synchronization heartbeat is detected for AD Connector - {0}."                                       -Option Constant -Scope script
Set-Variable EventMsgDomainIsNotReachable                     "Domain {0} is not reachable."                                                                                         -Option Constant -Scope script
Set-Variable EventMsgPwdSyncLocalConfigDisabled               "All AD Connectors are disabled."                                                                                      -Option Constant -Scope script
Set-Variable EventMsgPwdSyncLocalAndCloudConfigDifferent      "Local and cloud configurations are different. Local Enabled - {0}, Cloud Enabled - {1}."                              -Option Constant -Scope script
Set-Variable EventMsgConnectorAccountPwdSyncPermissionFailed  "AD Connector account does not have required permission for password hash synchronization. Domain - {0}."              -Option Constant -Scope script
Set-Variable EventMsgRestartPwdSyncChannelSuccess             "Password Hash Synchronization is successfully restarted for AD Connector - {0}."                                      -Option Constant -Scope script
Set-Variable EventMsgRestartPwdSyncChannelFailure             "Password Hash Synchronization could NOT be restarted for AD Connector - {0}."                                         -Option Constant -Scope script
Set-Variable EventMsgConnectorPwdSyncStopped                  "Password Hash Synchronization is NOT running for AD Connector - {0}."                                                 -Option Constant -Scope script
Set-Variable EventMsgPwdSyncActivityWithoutHeartbeat          "There is password hash synchronization activity but NO heartbeat for AD Connector - {0}."                             -Option Constant -Scope script

Set-Variable EventMsgPersistentDomainFailure                  "Password hash synchronization agent is continuously getting domain failures. Domain - {0}"                            -Option Constant -Scope script
Set-Variable EventMsgTemporaryDomainFailure                   "Password hash synchronization agent had domain failure. Domain - {0}"                                                 -Option Constant -Scope script
Set-Variable EventMsgCannotResolveDomainController            "Password hash synchronization agent could not resolve a domain controller. Domain - {0}"                              -Option Constant -Scope script
Set-Variable EventMsgCannotLocateDomainController             "Password hash synchronization agent could not locate a domain controller. Domain - {0}"                               -Option Constant -Scope script
Set-Variable EventMsgCannotBindToDomainController             "Password hash synchronization agent could not bind to a domain controller. Domain - {0}"                              -Option Constant -Scope script
Set-Variable EventMsgPersistentRPCError                       "Password hash synchronization agent is continuously getting RPC errors. Domain - {0}"                                 -Option Constant -Scope script
Set-Variable EventMsgTemporaryRPCError                        "Password hash synchronization agent had RPC error. Domain - {0}"                                                      -Option Constant -Scope script
Set-Variable EventMsgPersistentLDAPConnectionError            "Password hash synchronization agent is continuously getting LDAP connection errors. Domain - {0}"                     -Option Constant -Scope script
Set-Variable EventMsgTemporaryLDAPConnectionError             "Password hash synchronization agent had LDAP connection error. Domain - {0}"                                          -Option Constant -Scope script
Set-Variable EventMsgPasswordSyncCryptographicException       "Password hash synchronization agent had System.Security.Cryptography.CryptographicException. Domain - {0}"            -Option Constant -Scope script
Set-Variable EventMsgPasswordSyncInternalError                "Password hash synchronization agent had internal error. Domain - {0}"                                                 -Option Constant -Scope script

Set-Variable EventMsgPwdSyncBatchAWSFailure                                           "Password Hash Sync Batch AWS API Call Failure."                                                                            -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailurePwdSyncCloudConfigNotActivated             "Password Hash Sync Batch AWS API Call Failure - Password hash sync cloud configuration is NOT activated."                  -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureIdentitySyncCloudConfigNotActivated        "Password Hash Sync Batch AWS API Call Failure - Identity sync cloud configuration is NOT activated."                       -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureAADAccessDenied                            "Password Hash Sync Batch AWS API Call Failure - Access to Azure Active Directory has been denied."                         -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureEndpointNotFoundException                  "Password Hash Sync Batch AWS API Call Failure - System.ServiceModel.EndpointNotFoundException."                            -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureSecurityNegotiationException               "Password Hash Sync Batch AWS API Call Failure - System.ServiceModel.Security.SecurityNegotiationException."                -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureAdalServiceException                       "Password Hash Sync Batch AWS API Call Failure - Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException."     -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureAdalServiceExUserRealmDiscoveryFailed      "Password Hash Sync Batch AWS API Call Failure - User realm discovery failed (AdalServiceException)."                       -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureAdalServiceExAccountMustBeAddedToTenant    "Password Hash Sync Batch AWS API Call Failure - Account must be added to the AAD tenant (AdalServiceException)."           -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureAdalServiceExOldPasswordUsed               "Password Hash Sync Batch AWS API Call Failure - Old password is used for authentication (AdalServiceException)."           -Option Constant -Scope script   
Set-Variable EventMsgPwdSyncBatchAWSFailureAdalServiceExAccountDisabled               "Password Hash Sync Batch AWS API Call Failure - User account is disabled (AdalServiceException)."                          -Option Constant -Scope script
Set-Variable EventMsgPwdSyncBatchAWSFailureAdalServiceExInvalidUsernameOrPwd          "Password Hash Sync Batch AWS API Call Failure - Invalid username or password (AdalServiceException)."                      -Option Constant -Scope script  
Set-Variable EventMsgPwdSyncBatchAWSFailureAdalServiceExTenantNotFound                "Password Hash Sync Batch AWS API Call Failure - Tenant not found (AdalServiceException)."                                  -Option Constant -Scope script

Set-Variable EventMsgPwdSyncPingAWSFailure                                            "Password Hash Sync Ping AWS API Call Failure."                                                                             -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailurePwdSyncCloudConfigNotActivated              "Password Hash Sync Ping AWS API Call Failure - Password hash sync cloud configuration is NOT activated."                   -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureIdentitySyncCloudConfigNotActivated         "Password Hash Sync Ping AWS API Call Failure - Identity sync cloud configuration is NOT activated."                        -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureAADAccessDenied                             "Password Hash Sync Ping AWS API Call Failure - Access to Azure Active Directory has been denied."                          -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureEndpointNotFoundException                   "Password Hash Sync Ping AWS API Call Failure - System.ServiceModel.EndpointNotFoundException."                             -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureSecurityNegotiationException                "Password Hash Sync Ping AWS API Call Failure - System.ServiceModel.Security.SecurityNegotiationException."                 -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureAdalServiceException                        "Password Hash Sync Ping AWS API Call Failure - Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException."      -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureAdalServiceExUserRealmDiscoveryFailed       "Password Hash Sync Ping AWS API Call Failure - User realm discovery failed (AdalServiceException)."                        -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureAdalServiceExAccountMustBeAddedToTenant     "Password Hash Sync Ping AWS API Call Failure - Account must be added to the AAD tenant (AdalServiceException)."            -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureAdalServiceExOldPasswordUsed                "Password Hash Sync Ping AWS API Call Failure - Old password is used for authentication (AdalServiceException)."            -Option Constant -Scope script   
Set-Variable EventMsgPwdSyncPingAWSFailureAdalServiceExAccountDisabled                "Password Hash Sync Ping AWS API Call Failure - User account is disabled (AdalServiceException)."                           -Option Constant -Scope script
Set-Variable EventMsgPwdSyncPingAWSFailureAdalServiceExInvalidUsernameOrPwd           "Password Hash Sync Ping AWS API Call Failure - Invalid username or password (AdalServiceException)."                       -Option Constant -Scope script  
Set-Variable EventMsgPwdSyncPingAWSFailureAdalServiceExTenantNotFound                 "Password Hash Sync Ping AWS API Call Failure - Tenant not found (AdalServiceException)."                                   -Option Constant -Scope script

Set-Variable EventMsgPersistentHealthTaskFailure                                      "Password hash synchronization agent is continuously getting health task failures. AD Connector - {0}."                     -Option Constant -Scope script
Set-Variable EventMsgTemporaryHealthTaskFailure                                       "Password hash synchronization agent had health task failure. AD Connector - {0}."                                          -Option Constant -Scope script

Set-Variable EventMsgIsPwdSyncGeneralDiagnosticsHelpful                               "Is password hash sync general diagnostics helpful: {0}"                                                                    -Option Constant -Scope script


Set-Variable EventMsgSingleObjectConnectorDisabled            "Password Hash Synchronization is disabled for AD Connector - {0}"                                                                                                    -Option Constant -Scope script
Set-Variable EventMsgSingleObjectDisconnector                 "The object is a disconnector, it does not have a link to the metaverse. AD Connector - {0}, DN - {1}"                                                                -Option Constant -Scope script
Set-Variable EventMsgSingleObjectNotSyncedToAADCS             "The object is not synced to the AAD connector space. AD Connector - {0}, DN - {1}"                                                                                   -Option Constant -Scope script
Set-Variable EventMsgSingleObjectNoADPwdSyncRule              "There is no password hash synchronization rule for AD connector space object. AD Connector - {0}, DN - {1}"                                                          -Option Constant -Scope script
Set-Variable EventMsgSingleObjectNoAADPwdSyncRule             "There is no password hash synchronization rule for target AAD connector space object. AD Connector - {0}, DN - {1}"                                                  -Option Constant -Scope script
Set-Variable EventMsgSingleObjectNoPwdHistory                 "Password Hash Synchronization agent does not have any password change history for the specified object. AD Connector - {0}, DN - {1}"                                -Option Constant -Scope script
Set-Variable EventMsgSingleObjectFilteredByTarget             "FilteredByTarget - Temporary password is filtered by target. AD Connector - {0}, DN - {1}, DateTime - {2}"                                                           -Option Constant -Scope script
Set-Variable EventMsgSingleObjectNotExported                  "TargetNotExportedToDirectory - The object in the AAD connector space has not yet been exported. AD Connector - {0}, DN - {1}, DateTime - {2}"                        -Option Constant -Scope script
Set-Variable EventMsgSingleObjectNoTargetConnection           "NoTargetConnection - The object is not synced to AAD connector space or password hash sync rule(s) are not available. AD Connector - {0}, DN - {1}, DateTime - {2}"  -Option Constant -Scope script
Set-Variable EventMsgSingleObjectSuccess                      "Password hash is synchronized successfully. AD Connector - {0}, DN - {1}, DateTime - {2}"                                                                            -Option Constant -Scope script
Set-Variable EventMsgSingleObjectOtherFailure                 "Password hash synchronization is failed. AD Connector - {0}, DN - {1}, DateTime - {2}, Status - {3}"                                                                 -Option Constant -Scope script
Set-Variable EventMsgIsPwdSyncSingleObjectDiagnosticsHelpful  "Is password hash sync single object diagnostics helpful: {0}"                                                                                                        -Option Constant -Scope script

Function GetPasswordHashSyncCloudConfiguration
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $AADConnector
    )

    Try
    {
        $aadCompanyFeatures = Get-ADSyncAADCompanyFeature
        $passwordHashSyncCloudConfiguration = $aadCompanyFeatures.PasswordHashSync
        Write-Output $passwordHashSyncCloudConfiguration
    }
    Catch
    {
        Write-Output $null
    }
}

Function GetADConnectorPasswordSyncConfiguration
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $ADConnector
    )

    Try
    {
        $adConnectorPasswordSyncConfig = Get-ADSyncAADPasswordSyncConfiguration -SourceConnector $ADConnector.Name
        Write-Output $adConnectorPasswordSyncConfig
    }
    Catch
    {
        Write-Output $null
    }
}

Function GetADConnectorLatestPingEvent
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $ADConnector
    )
    
    $pingEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 654 -After (Get-Date).AddHours(-3) -ErrorAction SilentlyContinue | 
                  Where-Object { $_.Message.ToUpperInvariant().Contains($ADConnector.Identifier.ToString("D").ToUpperInvariant()) } | 
                  Sort-Object  { $_.TimeWritten } -Descending

    if ($pingEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $pingEvents[0]
    }
}

Function GetADConnectorPasswordHashSyncLatestActivityEvent
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $ADConnector
    )

    #
    # The events in the following indicates that there is an ongoing password hash sync activity for the given AD Connector.
    #
    $passwordHashSyncActivityEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue |
                                      Where-Object { $_.InstanceId -eq 601 -or
                                                     $_.InstanceId -eq 609 -or
                                                     $_.InstanceId -eq 610 -or
                                                     $_.InstanceId -eq 611 -or
                                                     $_.InstanceId -eq 615 -or
                                                     $_.InstanceId -eq 617 -or
                                                     $_.InstanceId -eq 618 -or
                                                     $_.InstanceId -eq 619 -or
                                                     $_.InstanceId -eq 620 -or
                                                     $_.InstanceId -eq 621 -or
                                                     $_.InstanceId -eq 650 -or 
                                                     $_.InstanceId -eq 651 -or
                                                     $_.InstanceId -eq 652 -or
                                                     $_.InstanceId -eq 653 -or
                                                     $_.InstanceId -eq 654 -or
                                                     $_.InstanceId -eq 655 -or
                                                     $_.InstanceId -eq 656 -or 
                                                     $_.InstanceId -eq 657 -or
                                                     $_.InstanceId -eq 660 -or
                                                     $_.InstanceId -eq 661 -or
                                                     $_.InstanceId -eq 662 -or
                                                     $_.InstanceId -eq 663 } |
                                      Where-Object { $_.Message.ToUpperInvariant().Contains($ADConnector.Identifier.ToString("D").ToUpperInvariant()) } |
                                      Sort-Object  { $_.TimeWritten } -Descending

    if ($passwordHashSyncActivityEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $passwordHashSyncActivityEvents[0]
    }
}

Function RestartADConnectorPasswordHashSyncChannel
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $ADConnector,

        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $AADConnector
    )

    try
    {
        Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $ADConnector.Name -TargetConnector $AADConnector.Name -Enable $false
        Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $ADConnector.Name -TargetConnector $AADConnector.Name -Enable $true

        Start-Sleep -s 10

        WriteEventLog($EventIdRestartPwdSyncChannelSuccess)($EventMsgRestartPwdSyncChannelSuccess -f $ADConnector.Name)

        "Password Hash Synchronization is successfully restarted for AD Connector: $($ADConnector.Name)" | ReportOutput 
    }
    catch
    {
        WriteEventLog($EventIdRestartPwdSyncChannelFailure)($EventMsgRestartPwdSyncChannelFailure -f $ADConnector.Name)

        "Password Hash Synchronization could NOT be restarted for AD Connector: $($ADConnector.Name)" | ReportError
    }
}

Function GetCsObjectPasswordSyncRule
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.CsObject]
        [parameter(mandatory=$true)]
        $CsObject
    )

    $syncRules = Get-ADSyncRule

    foreach ($csObjectLink in $CsObject.Lineage)
    {
        Try
        {
            $syncRule = $syncRules | Where { $_.InternalId -eq $csObjectLink.SyncRuleInternalId }

            if ($syncRule.EnablePasswordSync -eq $true)
            {
                Write-Output $syncRule
                return
            }
        }
        Catch
        {
            Write-Warning "A sync rule with internalId `"$($csObjectLink.SyncRuleInternalId)`" referenced on the link was not found"
        }
    }

    Write-Output $null
}

Function GetCsObjectLog
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.CsObject]
        [parameter(mandatory=$true)]
        $CsObject,

        [int]
        [parameter(mandatory=$true)]
        $LogEntryCount
    )

    Try
    {
        $csObjectLogEntries = Get-ADSyncCSObjectLog -Identifier $CsObject.ObjectId -Count $LogEntryCount
        Write-Output $csObjectLogEntries
    }
    Catch
    {
        Write-Output $null
    }
}

#
# Test connectivity to a domain or domain controller
#
# Input parameter for domain : fully qualified name of the domain
#
# OR
#
# Input parameter for domain controller : host name of the domain controller
#
Function IsDomainReachable
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
    
    $port = 389
    $timeout = New-TimeSpan -Seconds 30

    $directoryIdentifier = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($Domain, $port)
    $ldapConnection = New-Object System.DirectoryServices.Protocols.LdapConnection($directoryIdentifier)
    $ldapConnection.AuthType = [System.DirectoryServices.Protocols.AuthType]::Anonymous
    $ldapConnection.AutoBind = $false
    $ldapConnection.Timeout = $timeout

    Try
    {
        $ldapConnection.Bind()
        Write-Output $true
    }
    Catch
    {
        Write-Output $false
    }

    $ldapConnection.Dispose()
}

#
# Check if password hash sync agent continuously fails to compute MD5 decryption key.
#
# Checks 667 error events
#
# Measure - 5+ failures in the last 2 hour
#
Function IsPersistentMD5Failure
{

    $passwordSyncMD5ErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 667 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                  Sort-Object  { $_.TimeWritten } -Descending

    $numberOfMD5FailuresInLastTwoHours = $passwordSyncMD5ErrorEvents.Length

    if ($numberOfMD5FailuresInLastTwoHours -gt 4)
    {
        $latestPasswordSyncMD5ErrorEventTime = GetDateTimeLocaleEnUs($passwordSyncMD5ErrorEvents[0].TimeGenerated)

        $errorStr = 
        "Password Hash Synchronization agent continuously fails to create a key for decryption. `n" +
        "If Federal Information Processing Standards (FIPS) policy is enabled, updating the configuration for the synchronization service (ADSync) may resolve the issue. `n" +
        "Please see: https://go.microsoft.com/fwlink/?linkid=875725 `n" +
        "Please check 667 error events in the application event logs for details `n" +
        "The latest 667 error event is generated at: $latestPasswordSyncMD5ErrorEventTime UTC "
        
        $errorStr | ReportError

        Write-Host "`r`n"

        Write-Output $true
    }
    else
    {
        Write-Output $false
    }
}

#
# Check if password hash sync agent continuously gets failures for the given domain
#
# Checks 611 error events
#
# Measure - 5+ domain failures in the last 2 hours for the same domain
#
Function IsPersistentDomainFailure
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $DomainName = " " + $Domain.ToUpperInvariant()


    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object {($_.Message.ToUpperInvariant().Contains($DomainName))} |
                                     Sort-Object  { $_.TimeWritten } -Descending

    $numberOfDomainFailuresInLastTwoHours = $domainPasswordSyncErrorEvents.Length

    if ($numberOfDomainFailuresInLastTwoHours -gt 4)
    {
        WriteEventLog($EventIdPersistentDomainFailure)($EventMsgPersistentDomainFailure -f $Domain)

        $latestDomainPasswordSyncErrorEventTime = GetDateTimeLocaleEnUs($domainPasswordSyncErrorEvents[0].TimeGenerated)

        $errorStr =
        "`tPassword Hash Synchronization agent is continuously getting failures for domain `"$($Domain)`" `n" +
        "`tPlease check 611 error events in the application event logs for details `n" +
        "`tThe latest 611 error event for the domain `"$($Domain)`" is generated at: $latestDomainPasswordSyncErrorEventTime UTC `n"

        $errorStr | ReportError
        Write-Host "`r`n"

        Write-Output $true
    }
    elseif ($numberOfDomainFailuresInLastTwoHours -gt 0)
    {
        WriteEventLog($EventIdTemporaryDomainFailure)($EventMsgTemporaryDomainFailure -f $Domain)

        Write-Output $false
    }
    else
    {
        Write-Output $false
    }
}

#
# Check if password hash sync agent continuously gets failures for Health Task
#
# Checks 662 error events
#
# Measure - 5+ health task failures in the last 2 hours for the same AD Connector
#
Function IsPersistentHealthTaskFailure
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $ADConnector
    )

    $adConnectorPwdSyncHealthTaskErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 662 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue |
                                               Where-Object { $_.Message.ToUpperInvariant().Contains($ADConnector.Identifier.ToString("D").ToUpperInvariant()) } |
                                               Sort-Object  { $_.TimeWritten } -Descending

    $numberOfHealthTaskFailuresInLastTwoHours = $adConnectorPwdSyncHealthTaskErrorEvents.Length

    if ($numberOfHealthTaskFailuresInLastTwoHours -gt 4)
    {
        WriteEventLog($EventIdPersistentHealthTaskFailure)($EventMsgPersistentHealthTaskFailure -f $ADConnector.Name)

        $latestADConnectorPwdSyncHealthTaskErrorEventTime = GetDateTimeLocaleEnUs($adConnectorPwdSyncHealthTaskErrorEvents[0].TimeGenerated)

        $errorStr = "`r`n" +
        "`tPassword Hash Synchronization agent is continuously getting failures for AD Connector `"$($ADConnector.Name)`" `n" +
        "`tPlease check 662 and 6900 error events in the application event logs for details `n" +
        "`tPlease make sure AAD connector account is added to AAD Tenant, and username and password for this account are valid `n" +
        "`tThe latest 662 error event for the AD Connector `"$($ADConnector.Name)`" is generated at: $latestADConnectorPwdSyncHealthTaskErrorEventTime UTC `n" +
        "`r`n"

        $errorStr | ReportError

        $errorStr = "`r`n" +
        "`tPassword Hash Synchronization agent is continuously getting failures for AD Connector `"$($ADConnector.Name)`" `n" +
        "`tPlease check 662 and 6900 error events in the application event logs for details `n" +
        "`tPlease make sure AAD connector account is added to AAD Tenant, and username and password for this account are valid `n" +
        "`tThe latest 662 error event for the AD Connector `"$($ADConnector.Name)`" is generated at: $latestADConnectorPwdSyncHealthTaskErrorEventTime UTC `n" +
        "`r`n"

        $errorStr | ReportError
        Write-Output $true
    }
    elseif ($numberOfHealthTaskFailuresInLastTwoHours -gt 0)
    {
        WriteEventLog($EventIdTemporaryHealthTaskFailure)($EventMsgTemporaryHealthTaskFailure -f $ADConnector.Name)

        Write-Output $false
    }
    else
    {
        Write-Output $false
    }
 }

#
# Check if password hash sync agent fails for the given domain due to missing password hash sync AD permissions for AD Connector account
#
Function GetADConnectorAccountLatestPasswordSyncPermissionFailedEvent
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )    
    
    $Domain = " " + $Domain.ToUpperInvariant()
    $passwordSyncPermissionRPCErrorCode = "8453".ToUpperInvariant()
    $permissionErrorCallStackEntry = "DrsRpcConnection.OnGetChanges".ToUpperInvariant()
    
    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object { $_.Message.ToUpperInvariant().Contains($Domain) -and 
                                                    $_.Message.ToUpperInvariant().Contains($passwordSyncPermissionRPCErrorCode) -and
                                                    $_.Message.ToUpperInvariant().Contains($permissionErrorCallStackEntry) } |
                                     Sort-Object  { $_.TimeWritten } -Descending

    if ($domainPasswordSyncErrorEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $domainPasswordSyncErrorEvents[0]
    }
}

#
# Check if password hash sync agent fails to resolve a domain controller for the given domain
#
#
# Reason 1 - Unable to retrieve source domain information
#
# Reason 2 - Unable to resolve source host name
#
Function GetLatestFailureToResolveDomainControllerEvent
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $Domain = " " + $Domain.ToUpperInvariant()

    #
    # event 611 - unable to retrieve source domain information
    #
    $unableToRetrieveSourceDomainInfoCallStackEntry1_611 = "DrsConnection.CreateSourceDomainInformation".ToUpperInvariant()
    $unableToRetrieveSourceDomainInfoCallStackEntry2_611 = "DrsConnection.ReadServerGuids".ToUpperInvariant()

    #
    # event 611 - unable to resolve source host name
    #
    $unableToResolveSourceHostNameCallStackEntry1_611 = "DrsConnection.CreateSourceDomainInformation".ToUpperInvariant()
    $unableToResolveSourceHostNameCallStackEntry2_611 = "DrsConnection.ResolveSourceHostName".ToUpperInvariant()
    $unableToResolveSourceHostNameCallStackEntry3_611 = "Dns.GetHostEntry".ToUpperInvariant()

    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object {($_.Message.ToUpperInvariant().Contains($Domain)) -and
                                                   (
                                                        (($_.Message.ToUpperInvariant().Contains($unableToRetrieveSourceDomainInfoCallStackEntry1_611))  -and
                                                         ($_.Message.ToUpperInvariant().Contains($unableToRetrieveSourceDomainInfoCallStackEntry2_611))) -or
                                                        
                                                        (($_.Message.ToUpperInvariant().Contains($unableToResolveSourceHostNameCallStackEntry1_611)) -and
                                                         ($_.Message.ToUpperInvariant().Contains($unableToResolveSourceHostNameCallStackEntry2_611)) -and
                                                         ($_.Message.ToUpperInvariant().Contains($unableToResolveSourceHostNameCallStackEntry3_611)))
                                                    )} |
                                     Sort-Object  { $_.TimeWritten } -Descending

    if ($domainPasswordSyncErrorEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $domainPasswordSyncErrorEvents[0]
    }
}

#
# Check if password hash sync agent fails to locate a domain controller for the given domain after successfully
# retrieving source domain information and resolving source host name
#
Function GetLatestFailureToLocateDomainControllerEvent
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $Domain = " " + $Domain.ToUpperInvariant()

    #
    # event 611 - Unable to locate a domain controller
    #
    $unableToLocateDomainControllerCallStackEntry1_611 = "DrsConnection.ResolveDomainController".ToUpperInvariant()
    $unableToLocateDomainControllerCallStackEntry2_611 = "DirectoryUtility.FindPreferredDC".ToUpperInvariant()
    $unableToLocateDomainControllerCallStackEntry3_611 = "Domain.GetDomain".ToUpperInvariant()
    
    $incorrectUsernameOrPasswordCallStackEntryBind = "DirectoryEntry.Bind".ToUpperInvariant()

    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object {($_.Message.ToUpperInvariant().Contains($Domain)) -and
                                                   (
                                                        (($_.Message.ToUpperInvariant().Contains($unableToLocateDomainControllerCallStackEntry1_611))   -and
                                                         !($_.Message.ToUpperInvariant().Contains($incorrectUsernameOrPasswordCallStackEntryBind))      -and
                                                         (($_.Message.ToUpperInvariant().Contains($unableToLocateDomainControllerCallStackEntry2_611)) -or
                                                          ($_.Message.ToUpperInvariant().Contains($unableToLocateDomainControllerCallStackEntry3_611))))
                                                    )} |
                                     Sort-Object  { $_.TimeWritten } -Descending

    if ($domainPasswordSyncErrorEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $domainPasswordSyncErrorEvents[0]
    }
}

#
# Check if password hash sync agent fails for the given domain due to incorrect username or password of AD Connector account
#
Function GetADConnectorAccountLatestUsernameOrPasswordIncorrectEvent
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $Domain = " " + $Domain.ToUpperInvariant()
    $incorrectUsernameOrPasswordCallStackEntryBind = "DirectoryEntry.Bind".ToUpperInvariant()

    #
    # event 611 - failure to resolve a domain controller
    #
    $incorrectUsernameOrPasswordCallStackEntry_611 = "DrsConnection.ResolveDomainController".ToUpperInvariant()
    
    #
    # event 612 - failure to get domain naming context information during initialization of password hash sync channel
    #
    $incorrectUsernameOrPasswordCallStackEntry_612 = "DirectoryUtility.GetDomainNamingContextInfo".ToUpperInvariant()


    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object { (($_.EventID -eq 611) -and
                                                     ($_.Message.ToUpperInvariant().Contains($Domain)) -and
                                                     ($_.Message.ToUpperInvariant().Contains($incorrectUsernameOrPasswordCallStackEntry_611))  -and
                                                     ($_.Message.ToUpperInvariant().Contains($incorrectUsernameOrPasswordCallStackEntryBind)))  -or

                                                     (($_.EventID -eq 612) -and
                                                      ($_.Message.ToUpperInvariant().Contains($Domain)) -and
                                                      ($_.Message.ToUpperInvariant().Contains($incorrectUsernameOrPasswordCallStackEntry_612))  -and
                                                      ($_.Message.ToUpperInvariant().Contains($incorrectUsernameOrPasswordCallStackEntryBind))) } |
                                     Sort-Object  { $_.TimeWritten } -Descending

    if ($domainPasswordSyncErrorEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $domainPasswordSyncErrorEvents[0]
    }
}

#
# Check if password hash sync agent continuously gets RPC errors when connecting to the given domain
#
# Measure - 5+ RPC errors in the last 2 hours for the same domain
#
Function IsPersistentRPCError
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $DomainName = " " + $Domain.ToUpperInvariant()

    #
    # event 611 - RPC Error
    #
    $passwordSyncRPCErrorEntry = "RPC Error".ToUpperInvariant()

    #
    # RPC Error 8453 which is an indicator of AD Connector Account password hash sync permission problem is
    # going to be excluded as it is already tracked.
    #
    $passwordSyncPermissionRPCErrorCode = "8453".ToUpperInvariant()
    $permissionErrorCallStackEntry = "DrsRpcConnection.OnGetChanges".ToUpperInvariant()


    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object {($_.Message.ToUpperInvariant().Contains($DomainName)) -and
                                                   ($_.Message.ToUpperInvariant().Contains($passwordSyncRPCErrorEntry)) -and
                                                   !($_.Message.ToUpperInvariant().Contains($passwordSyncPermissionRPCErrorCode)) -and
                                                   !($_.Message.ToUpperInvariant().Contains($permissionErrorCallStackEntry))} |
                                     Sort-Object  { $_.TimeWritten } -Descending
    
    $numberOfRPCErrorsInLastTwoHours = $domainPasswordSyncErrorEvents.Length

    if ($numberOfRPCErrorsInLastTwoHours -gt 4)
    {
        WriteEventLog($EventIdPersistentRPCError)($EventMsgPersistentRPCError -f $Domain)

        $latestDomainRPCErrorEventTime = GetDateTimeLocaleEnUs($domainPasswordSyncErrorEvents[0].TimeGenerated)

        $errorStr = 
        "`tPassword Hash Synchronization agent is continuously getting RPC errors from domain `"$($Domain)`" `n" +
        "`tPlease setup reliable preferred domain controllers. Please see `"Connectivity problems`" section at https://go.microsoft.com/fwlink/?linkid=847231 `n" +
        "`tPlease check 611 error events in the application event logs for details `n" +
        "`tThe latest RPC error event for the domain `"$($Domain)`" is generated at: $latestDomainRPCErrorEventTime UTC `n" +
        "`r`n"

        $errorStr | ReportError
        Write-Output $true
    }
    elseif ($numberOfRPCErrorsInLastTwoHours -gt 0)
    {
        WriteEventLog($EventIdTemporaryRPCError)($EventMsgTemporaryRPCError -f $Domain)

        Write-Output $false
    }
    else
    {
        Write-Output $false
    }
}

#
# Check if password hash sync agent continuously gets LDAP connection errors from the given domain
#
# Measure - 5+ LDAP connection errors in the last 2 hours for the same domain
#
Function IsPersistentLDAPConnectionError
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $DomainName = " " + $Domain.ToUpperInvariant()

    #
    # event 611 - LdapException
    #
    $ldapExceptionEntry = ".LdapException".ToUpperInvariant()

    #
    # event 611 - DirectoryOperationException
    #
    $directoryOperationExceptionEntry = ".DirectoryOperationException".ToUpperInvariant()


    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object {($_.Message.ToUpperInvariant().Contains($DomainName)) -and
                                                   (($_.Message.ToUpperInvariant().Contains($ldapExceptionEntry)) -or
                                                    ($_.Message.ToUpperInvariant().Contains($directoryOperationExceptionEntry)))} |
                                     Sort-Object  { $_.TimeWritten } -Descending

    $numberOfLdapConnectionErrorsInLastTwoHours = $domainPasswordSyncErrorEvents.Length

    if ($numberOfLdapConnectionErrorsInLastTwoHours -gt 4)
    {
        WriteEventLog($EventIdPersistentLDAPConnectionError)($EventMsgPersistentLDAPConnectionError -f $Domain)

        $latestDomainLDAPConnectionErrorEventTime = GetDateTimeLocaleEnUs($domainPasswordSyncErrorEvents[0].TimeGenerated)

        $errorStr = 
        "`tPassword Hash Synchronization agent is continuously getting LDAP connection errors from the domain `"$($Domain)`" `n" +
        "`tPlease setup reliable preferred domain controllers. Please see `"Connectivity problems`" section at https://go.microsoft.com/fwlink/?linkid=847231 `n" +
        "`tPlease check 611 error events in the application event logs for details `n" +
        "`tThe latest LDAP Connection error event for the domain `"$($Domain)`" is generated at: $latestDomainLDAPConnectionErrorEventTime UTC `n" +
        "`r`n"

        $errorStr | ReportError

        Write-Output $true
    }
    elseif ($numberOfLdapConnectionErrorsInLastTwoHours -gt 0)
    {
        WriteEventLog($EventIdTemporaryLDAPConnectionError)($EventMsgTemporaryLDAPConnectionError -f $Domain)

        Write-Output $false
    }
    else
    {
        Write-Output $false
    }
}

#
# Check if password hash sync agent gets CryptographicException
#
Function GetLatestCryptographicExceptionEvent
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $Domain = " " + $Domain.ToUpperInvariant()

    #
    # event 611 - CryptographicException
    #
    $cryptographicExceptionEntry = ".CryptographicException".ToUpperInvariant()

    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object {($_.Message.ToUpperInvariant().Contains($Domain)) -and
                                                   ($_.Message.ToUpperInvariant().Contains($cryptographicExceptionEntry))} |
                                     Sort-Object  { $_.TimeWritten } -Descending

    if ($domainPasswordSyncErrorEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $domainPasswordSyncErrorEvents[0]
    }
}

#
# Check password hash sync agent internal errors
#
# Most failing calls: FimNotificationManager.GetRetryObjects, FimNotificationManager.UpdateRetryStatus
#
Function GetLatestInternalErrorEvent
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $Domain
    )

    #
    # Differentiate the given domain from the others having the same suffix.
    #
    $Domain = " " + $Domain.ToUpperInvariant()

    $passwordSyncInternalFailureCallStackEntry1_611 = "FimNotificationManager.GetRetryObjects".ToUpperInvariant()
    $passwordSyncInternalFailureCallStackEntry2_611 = "FimNotificationManager.UpdateRetryStatus".ToUpperInvariant()

    $domainPasswordSyncErrorEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 611 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                     Where-Object {($_.Message.ToUpperInvariant().Contains($Domain)) -and
                                                   (($_.Message.ToUpperInvariant().Contains($passwordSyncInternalFailureCallStackEntry1_611)) -or
                                                   ($_.Message.ToUpperInvariant().Contains($passwordSyncInternalFailureCallStackEntry2_611)))} |
                                     Sort-Object  { $_.TimeWritten } -Descending


    if ($domainPasswordSyncErrorEvents -eq $null)
    {
        Write-Output $null
    }
    else
    {
        Write-Output $domainPasswordSyncErrorEvents[0]
    }
}

#
# Check if password hash sync agent fails on AWS API call to push password changes to AAD Tenant OR to ping AAD Tenant.
#
# Checks 652 and 655 error events
#
Function CheckLatestPasswordSyncAWSCallFailureEvents
{
    $passwordSyncCloudConfigNotActivatedEntry = "Error Code: 90".ToUpperInvariant()
    $identitySyncCloudConfigNotActivatedEntry = "Error Code: 15".ToUpperInvariant()
    $accessToAzureActiveDirectoryDeniedEntry  = "Error Code: 7".ToUpperInvariant()
    $endpointNotFoundExceptionEntry           = "EndpointNotFoundException".ToUpperInvariant()
    $securityNegotiationExceptionEntry        = "SecurityNegotiationException".ToUpperInvariant()
    
    #
    # Adal Service Exception and related errors
    #
    $adalServiceExceptionEntry                = ".AdalServiceException".ToUpperInvariant()
    $userRealmDiscoveryFailedEntry            = "user_realm_discovery_failed".ToUpperInvariant()
    $stsErrorCodeAccountMustBeAddedToTenant   = "AADSTS50034".ToUpperInvariant()
    $stsErrorCodeOldPasswordUsed              = "AADSTS50054".ToUpperInvariant()
    $stsErrorCodeAccountDisabled              = "AADSTS50057".ToUpperInvariant()
    $stsErrorCodeInvalidUsernameOrPassword    = "AADSTS50126".ToUpperInvariant()
    $stsErrorCodeTenantNotFound               = "AADSTS90002".ToUpperInvariant()

    $passwordSyncBatchAWSCallFailureEvents = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 652 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                             Sort-Object  { $_.TimeWritten } -Descending

    $passwordSyncPingAWSCallFailureEvents  = Get-EventLog -LogName "Application" -Source "Directory Synchronization" -InstanceId 655 -After (Get-Date).AddHours(-2) -ErrorAction SilentlyContinue | 
                                             Sort-Object  { $_.TimeWritten } -Descending

    
    if ($passwordSyncBatchAWSCallFailureEvents -ne $null)
    {
        WriteEventLog($EventIdPwdSyncBatchAWSFailure)($EventMsgPwdSyncBatchAWSFailure)

        $pwdSyncBatchAWSCallFailureTime = GetDateTimeLocaleEnUs($passwordSyncBatchAWSCallFailureEvents[0].TimeGenerated)

        $errorStr =
        "Password Hash Synchronization agent had an error while pushing password changes to AAD tenant at: $pwdSyncBatchAWSCallFailureTime UTC `n" +
        "Please make sure AAD connector account is added to AAD Tenant, and username and password for this account are valid. `n" +
        "Please check 652 error events in the application event logs for details `n" +
        "`r`n"

        $errorStr | ReportError

        $errorMessage = $passwordSyncBatchAWSCallFailureEvents[0].Message.ToUpperInvariant();

        if ($errorMessage.Contains($passwordSyncCloudConfigNotActivatedEntry))
        {
            WriteEventLog($EventIdPwdSyncBatchAWSFailurePwdSyncCloudConfigNotActivated)($EventMsgPwdSyncBatchAWSFailurePwdSyncCloudConfigNotActivated)             
        }
        elseif ($errorMessage.Contains($identitySyncCloudConfigNotActivatedEntry))
        {
            WriteEventLog($EventIdPwdSyncBatchAWSFailureIdentitySyncCloudConfigNotActivated)($EventMsgPwdSyncBatchAWSFailureIdentitySyncCloudConfigNotActivated)             
        }
        elseif ($errorMessage.Contains($accessToAzureActiveDirectoryDeniedEntry))
        {
            WriteEventLog($EventIdPwdSyncBatchAWSFailureAADAccessDenied)($EventMsgPwdSyncBatchAWSFailureAADAccessDenied)             
        }
        elseif ($errorMessage.Contains($endpointNotFoundExceptionEntry))
        {
            WriteEventLog($EventIdPwdSyncBatchAWSFailureEndpointNotFoundException)($EventMsgPwdSyncBatchAWSFailureEndpointNotFoundException)
        }
        elseif ($errorMessage.Contains($adalServiceExceptionEntry))
        {
            WriteEventLog($EventIdPwdSyncBatchAWSFailureAdalServiceException)($EventMsgPwdSyncBatchAWSFailureAdalServiceException)

            if ($errorMessage.Contains($userRealmDiscoveryFailedEntry))
            {
                WriteEventLog($EventIdPwdSyncBatchAWSFailureAdalServiceExUserRealmDiscoveryFailed)($EventMsgPwdSyncBatchAWSFailureAdalServiceExUserRealmDiscoveryFailed)
            }
            elseif ($errorMessage.Contains($stsErrorCodeAccountMustBeAddedToTenant))
            {
                WriteEventLog($EventIdPwdSyncBatchAWSFailureAdalServiceExAccountMustBeAddedToTenant)($EventMsgPwdSyncBatchAWSFailureAdalServiceExAccountMustBeAddedToTenant)
            }
            elseif ($errorMessage.Contains($stsErrorCodeOldPasswordUsed))
            {
                WriteEventLog($EventIdPwdSyncBatchAWSFailureAdalServiceExOldPasswordUsed)($EventMsgPwdSyncBatchAWSFailureAdalServiceExOldPasswordUsed)
            }
            elseif ($errorMessage.Contains($stsErrorCodeAccountDisabled))
            {
                WriteEventLog($EventIdPwdSyncBatchAWSFailureAdalServiceExAccountDisabled)($EventMsgPwdSyncBatchAWSFailureAdalServiceExAccountDisabled)
            }
            elseif ($errorMessage.Contains($stsErrorCodeInvalidUsernameOrPassword))
            {
                WriteEventLog($EventIdPwdSyncBatchAWSFailureAdalServiceExInvalidUsernameOrPwd)($EventMsgPwdSyncBatchAWSFailureAdalServiceExInvalidUsernameOrPwd)
            }
            elseif ($errorMessage.Contains($stsErrorCodeTenantNotFound))
            {
                WriteEventLog($EventIdPwdSyncBatchAWSFailureAdalServiceExTenantNotFound)($EventMsgPwdSyncBatchAWSFailureAdalServiceExTenantNotFound)
            }
        }
        elseif ($errorMessage.Contains($securityNegotiationExceptionEntry))
        {
            WriteEventLog($EventIdPwdSyncBatchAWSFailureSecurityNegotiationException)($EventMsgPwdSyncBatchAWSFailureSecurityNegotiationException)
        }
    }

    if ($passwordSyncPingAWSCallFailureEvents -ne $null)
    {
        WriteEventLog($EventIdPwdSyncPingAWSFailure)($EventMsgPwdSyncPingAWSFailure)

        $pwdSyncPingAWSCallFailureTime = GetDateTimeLocaleEnUs($passwordSyncPingAWSCallFailureEvents[0].TimeGenerated)

        $errorStr = 
        "Password Hash Synchronization agent had an error while pinging AAD tenant at: $pwdSyncPingAWSCallFailureTime UTC `n" +
        "Please make sure AAD connector account is added to AAD Tenant, and username and password for this account are valid. `n" +
        "Please check 655 error events in the application event logs for details `n" +
        "`r`n"
        
        $errorStr | ReportError

        $errorMessage = $passwordSyncPingAWSCallFailureEvents[0].Message.ToUpperInvariant();

        if ($errorMessage.Contains($passwordSyncCloudConfigNotActivatedEntry))
        {
            WriteEventLog($EventIdPwdSyncPingAWSFailurePwdSyncCloudConfigNotActivated)($EventMsgPwdSyncPingAWSFailurePwdSyncCloudConfigNotActivated)             
        }
        elseif ($errorMessage.Contains($identitySyncCloudConfigNotActivatedEntry))
        {
            WriteEventLog($EventIdPwdSyncPingAWSFailureIdentitySyncCloudConfigNotActivated)($EventMsgPwdSyncPingAWSFailureIdentitySyncCloudConfigNotActivated)             
        }
        elseif ($errorMessage.Contains($accessToAzureActiveDirectoryDeniedEntry))
        {
            WriteEventLog($EventIdPwdSyncPingAWSFailureAADAccessDenied)($EventMsgPwdSyncPingAWSFailureAADAccessDenied)             
        }
        elseif ($errorMessage.Contains($endpointNotFoundExceptionEntry))
        {
            WriteEventLog($EventIdPwdSyncPingAWSFailureEndpointNotFoundException)($EventMsgPwdSyncPingAWSFailureEndpointNotFoundException)
        }
        elseif ($errorMessage.Contains($adalServiceExceptionEntry))
        {
             WriteEventLog($EventIdPwdSyncPingAWSFailureAdalServiceException)($EventMsgPwdSyncPingAWSFailureAdalServiceException)

            if ($errorMessage.Contains($userRealmDiscoveryFailedEntry))
            {
                WriteEventLog($EventIdPwdSyncPingAWSFailureAdalServiceExUserRealmDiscoveryFailed)($EventMsgPwdSyncPingAWSFailureAdalServiceExUserRealmDiscoveryFailed)
            }
            elseif ($errorMessage.Contains($stsErrorCodeAccountMustBeAddedToTenant))
            {
                WriteEventLog($EventIdPwdSyncPingAWSFailureAdalServiceExAccountMustBeAddedToTenant)($EventMsgPwdSyncPingAWSFailureAdalServiceExAccountMustBeAddedToTenant)
            }
            elseif ($errorMessage.Contains($stsErrorCodeOldPasswordUsed))
            {
                WriteEventLog($EventIdPwdSyncPingAWSFailureAdalServiceExOldPasswordUsed)($EventMsgPwdSyncPingAWSFailureAdalServiceExOldPasswordUsed)
            }
            elseif ($errorMessage.Contains($stsErrorCodeAccountDisabled))
            {
                WriteEventLog($EventIdPwdSyncPingAWSFailureAdalServiceExAccountDisabled)($EventMsgPwdSyncPingAWSFailureAdalServiceExAccountDisabled)
            }
            elseif ($errorMessage.Contains($stsErrorCodeInvalidUsernameOrPassword))
            {
                WriteEventLog($EventIdPwdSyncPingAWSFailureAdalServiceExInvalidUsernameOrPwd)($EventMsgPwdSyncPingAWSFailureAdalServiceExInvalidUsernameOrPwd)
            }
            elseif ($errorMessage.Contains($stsErrorCodeTenantNotFound))
            {
                WriteEventLog($EventIdPwdSyncPingAWSFailureAdalServiceExTenantNotFound)($EventMsgPwdSyncPingAWSFailureAdalServiceExTenantNotFound)
            }
        }
        elseif ($errorMessage.Contains($securityNegotiationExceptionEntry))
        {
            WriteEventLog($EventIdPwdSyncPingAWSFailureSecurityNegotiationException)($EventMsgPwdSyncPingAWSFailureSecurityNegotiationException)
        }
    }
}

#
# Check if verbose logging is turned on for password hash sync
#
Function CheckVerboseLoggingForPHS
{
    $adSyncRegKey = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\AD Sync"
    $adSyncPath = $adSyncRegKey.Location
    $adSyncConfigurationPath = [System.IO.Path]::Combine($adSyncPath, "Bin\miiserver.exe.config")

    [xml]$adSyncConfiguration = Get-Content $adSyncConfigurationPath
    $passwordSyncLogging = $adSyncConfiguration.configuration.'system.diagnostics'.sources.source | ? { $_.name -eq 'passwordSync'}
    
    if($passwordSyncLogging -and $passwordSyncLogging.switchValue -eq 'Verbose')
    {
        ReportWarning "Verbose logging is enabled for Password Hash Synchronization, this can very quickly fill up a disk. Review configuration at '$adSyncConfigurationPath'."
    }
}


Function ReportPartitionPasswordSyncState
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.PartitionPasswordSyncState]
        [parameter(mandatory=$true)]
        $PartitionPasswordSyncState
    )

    $partitionPasswordSyncLastCycleStatus = $partitionPasswordSyncState.PasswordSyncLastCycleStatus.ToString()
    
    $culture = New-Object System.Globalization.CultureInfo 'en-us'
    $partitionPasswordSyncLastSuccessfulCycleStartDateTime = $partitionPasswordSyncState.PasswordSyncLastSuccessfulCycleStartTimestamp.ToString($culture)
    $partitionPasswordSyncLastSuccessfulCycleEndDateTime = $partitionPasswordSyncState.PasswordSyncLastSuccessfulCycleEndTimestamp.ToString($culture)

    if ($partitionPasswordSyncLastCycleStatus -eq "None")
    {
        "`tPassword Hash Synchronization agent has never attempted to synchronize passwords from this directory partition." | ReportWarning
    }
    elseif ($partitionPasswordSyncLastCycleStatus -eq "InProgress")
    {
        "`tPassword Hash Synchronization agent is currently synchronizing passwords from this directory partition." | ReportWarning

        $partitionPasswordSyncLastCycleStartDateTime = $partitionPasswordSyncState.PasswordSyncLastCycleStartTimestamp.ToString($culture)
        "`tPassword Hash Synchronization is in progress and started at: $partitionPasswordSyncLastCycleStartDateTime UTC" | ReportWarning
    }
    elseif ($partitionPasswordSyncState.PasswordSyncLastSuccessfulCycleStartTimestamp.Year -eq 1)
    {
        "`tPassword Hash Synchronization agent has never successfully completed synchronizing passwords from this directory partition." | ReportWarning
    }
    else
    {
        "`tLast successful attempt to synchronize passwords from this directory partition started at: $partitionPasswordSyncLastSuccessfulCycleStartDateTime UTC and ended at: $partitionPasswordSyncLastSuccessfulCycleEndDateTime UTC" |
        ReportOutput -PropertyName "Last successfull Passwords Sync from this directory partition Start Time " -PropertyValue "$partitionPasswordSyncLastSuccessfulCycleStartDateTime UTC"
        ReportOutput -PropertyName "Last successfull Passwords Sync from this directory partition End Time" -PropertyValue "$partitionPasswordSyncLastSuccessfulCycleEndDateTime UTC"
    }
}

Function DiagnoseADConnectivity
{
    param
    (
        [Microsoft.IdentityManagement.PowerShell.ObjectModel.Connector]
        [parameter(mandatory=$true)]
        $ADConnector
    )

    Write-Host "`r`n"
    Write-Host "`tDirectory Partitions:"
    Write-Host "`t====================="

    if ($ADConnector.Partitions.Count -eq 0)
    {
        "`tNo directory partition is found for AD Connector - $($ADConnector.Name)" | ReportError
        Write-Host "`r`n"
        return
    }

    #
    # Get password hash sync state for all directory partitions.
    #
    $partitionPasswordSyncStateList = Get-ADSyncPartitionPasswordSyncState

    foreach ($partition in $ADConnector.Partitions)
    {
        Write-Host "`tDirectory Partition - $($partition.Name)"

        # Check if directory partition is a domain
        if (-not $partition.IsDomain)
        {
            "`tDirectory partition `"$($partition.Name)`" is not a domain" | Write-Host -fore Yellow
            Write-Host "`r`n"

            if ($isNonInteractiveMode)
            {
                continue
            }

            # Check if corresponding AD partition is a domain
            $adObject = Search-ADSyncDirectoryObjects -AdConnectorId $ADConnector.Identifier -LdapFilter "(distinguishedName=$($partition.DN))" -SearchScope Subtree -SizeLimit 1

            if ($adObject -ne $null -and $adObject.Count -gt 0 -and (IsObjectTypeMatch($adObject[0])("domain")))
            {
                $domainChoiceOptions = [System.Management.Automation.Host.ChoiceDescription[]] @("&Yes", "&No")
                $setDomainFlag = !($host.UI.PromptForChoice("Set domain flag on partition", "To avoid password sync issues it is recommended that the domain flag for the partition be set to true in the Connector configuration. Is it okay to set it to true now?", $domainChoiceOptions, 0))

                if ($setDomainFlag)
                {
                    try
                    {
                        # Set IsDomain flag in AD Connector Partition
                        $adPartionI = $ADConnector.Partitions.IndexOf($partition)
                        $ADConnector.Partitions[$adPartionI].IsDomain = $true

                        # Adding AD Connector configuration
                        Add-ADSyncConnector -Connector $ADConnector
                        Write-Output "INFO: AD Partition `"$($partition.Name)`" set as Domain partition successfully."
                    }
                    catch
                    {
                        Write-Error "An error occurred setting the domain flag on the partition: $($_.Exception.Message)"
                        continue
                    }
                }
                else
                {
                    continue
                }
            }
            else
            {
                continue
            }
        }
        
        # Check if directory partition is selected for synchronization
        if (-not $partition.Selected)
        {
            "`tDomain `"$($partition.Name)`" is excluded from synchronization" | Write-Host -fore Yellow
            Write-Host "`r`n"
            continue
        }

        #
        # Check if password hash sync agent continuously gets domain failures
        #
        IsPersistentDomainFailure($partition.Name)

        #
        # Check if password hash sync agent fails to RESOLVE a domain controller for the current directory partition
        # since it is unable to retrieve source domain information OR unable to resolve source host name
        #
        $unableToResolveDomainControllerEvent = GetLatestFailureToResolveDomainControllerEvent($partition.Name)
        
        if ($unableToResolveDomainControllerEvent -ne $null)
        {
            WriteEventLog($EventIdCannotResolveDomainController)($EventMsgCannotResolveDomainController -f $partition.Name)

            $unableToResolveDomainControllerIssueTime = GetDateTimeLocaleEnUs($unableToResolveDomainControllerEvent.TimeGenerated)
    
            $errString = "`tPassword Hash Synchronization agent had a problem to resolve a domain controller in the domain `"$($partition.Name)`" at: $unableToResolveDomainControllerIssueTime UTC `n" +
            "`tPlease check 611 error events in the application event logs for details `n" +
            "`r`n"
            $errString | ReportError

        }

        #
        # Check if password hash sync agent fails to LOCATE a domain controller for the current directory partition
        # after successfully retrieving the source domain information and resolving source host name
        #
        $unableToLocateDomainControllerEvent = GetLatestFailureToLocateDomainControllerEvent($partition.Name)

        if ($unableToLocateDomainControllerEvent -ne $null)
        {
            WriteEventLog($EventIdCannotLocateDomainController)($EventMsgCannotLocateDomainController -f $partition.Name)

            $unableToLocateDomainControllerIssueTime = GetDateTimeLocaleEnUs($unableToLocateDomainControllerEvent.TimeGenerated)

            $errString = 
            "`tPassword Hash Synchronization agent had a problem to locate a domain controller in the domain `"$($partition.Name)`" at: $unableToLocateDomainControllerIssueTime UTC `n" +
            "`tPlease setup reliable preferred domain controllers. Please see `"Connectivity problems`" section at https://go.microsoft.com/fwlink/?linkid=847231 `n" +
            "`tPlease check 611 error events in the application event logs for details `n" +
            "`r`n"

            $errString | ReportError

        }

        #
        # Check if AD Connector account has incorrect username or password problem for the directory partition
        #
        $incorrectUsernameOrPasswordEvent = GetADConnectorAccountLatestUsernameOrPasswordIncorrectEvent($partition.Name)

        if ($incorrectUsernameOrPasswordEvent -ne $null)
        {
            WriteEventLog($EventIdCannotBindToDomainController)($EventMsgCannotBindToDomainController -f $partition.Name)

            $incorrectUsernameOrPasswordIssueTime = GetDateTimeLocaleEnUs($incorrectUsernameOrPasswordEvent.TimeGenerated)

            $errString =
            "`tPassword Hash Synchronization agent had a problem about connecting to a domain controller in the domain `"$($partition.Name)`" at: $incorrectUsernameOrPasswordIssueTime UTC `n" +
            "`tPlease make sure AD Connector account username and password are correct `n" +
            "`tIn case the problem continues, then please setup reliable preferred domain controllers. Please see `"Connectivity problems`" section at https://go.microsoft.com/fwlink/?linkid=847231 `n" +
            "`tPlease check 611 error events in the application event logs for details `n" +
            "`r`n"

            $errString | ReportError
        }

        # Check if AD Connector account has password hash sync permission problem for the directory partition
        $passwordSyncPermissionFailedEvent = GetADConnectorAccountLatestPasswordSyncPermissionFailedEvent($partition.Name)
        
        if ($passwordSyncPermissionFailedEvent -ne $null)
        {
            WriteEventLog($EventIdConnectorAccountPwdSyncPermissionFailed)($EventMsgConnectorAccountPwdSyncPermissionFailed -f $partition.Name)

            $passwordSyncPermissionFailTime = GetDateTimeLocaleEnUs($passwordSyncPermissionFailedEvent.TimeGenerated)

            $errString = 
            "`tAD Connector account had a Password Hash Synchronization permission problem for the domain `"$($partition.Name)`" at: $passwordSyncPermissionFailTime UTC `n" +
            "`tPlease see: https://go.microsoft.com/fwlink/?linkid=847234 `n" +
            "`tPlease check 611 error events in the application event logs for details `n" +
            "`r`n"
            
            $errString | ReportError
        }

        #
        # Check if password hash sync agent fails by repeated RPC errors while connecting to a domain controller
        #
        IsPersistentRPCError($partition.Name)

        #
        # Check if password hash sync agent fails by repeated LDAP connection errors while connecting to a domain controller
        #
        IsPersistentLDAPConnectionError($partition.Name)

        #
        # Check if password hash sync agent fails by CryptographicException
        #
        $cryptographicExceptionEvent = GetLatestCryptographicExceptionEvent($partition.Name)

        if ($cryptographicExceptionEvent -ne $null)
        {
            WriteEventLog($EventIdPasswordSyncCryptographicException)($EventMsgPasswordSyncCryptographicException -f $partition.Name)

            $cryptographicExceptionIssueTime = GetDateTimeLocaleEnUs($cryptographicExceptionEvent.TimeGenerated)

            $errString = 
            "`tPassword Hash Synchronization agent had System.Security.Cryptography.CryptographicException for the domain `"$($partition.Name)`" at: $cryptographicExceptionIssueTime UTC `n" +
            "`tPlease check 611 error events in the application event logs for details `n" +
            "`r`n"

            $errString | ReportError
        }

        #
        # Check if password hash sync agent fails by an internal exception
        #
        $internalErrorEvent = GetLatestInternalErrorEvent($partition.Name)

        if ($internalErrorEvent -ne $null)
        {
            WriteEventLog($EventIdPasswordSyncInternalError)($EventMsgPasswordSyncInternalError -f $partition.Name)
        }
        
        #
        # Find the password hash sync state for the given directory partition (domain) using its distinguished name.
        #
        $partitionPasswordSyncState = $partitionPasswordSyncStateList | Where-Object{$_.DN -eq $partition.DN}

        # Report password hash sync state for the directory partition.
        ReportPartitionPasswordSyncState($partitionPasswordSyncState)

        # Check if DC connection settings is configured to use only preferred domain controllers
        $onlyUsePreferredDC = ($partition.Parameters["dc-failover"].Value -eq 0)

        "`tOnly Use Preferred Domain Controllers: $onlyUsePreferredDC" |
        ReportOutput -PropertyName "Use Preferred Domain Controllers" -PropertyValue $onlyUsePreferredDC

        if (-not $onlyUsePreferredDC)
        {
            Write-Host "`tChecking connectivity to the domain..."

            # Test connectivity to domain
            $isDomainReachable = IsDomainReachable($partition.Name)
            
            if ($isDomainReachable -eq $true)
            {
                "`tDomain `"$($partition.Name)`" is reachable" | ReportOutput 
            }
            else
            {
                WriteEventLog($EventIdDomainIsNotReachable)($EventMsgDomainIsNotReachable -f ($partition.Name))

                "`tDomain `"$($partition.Name)`" is not reachable" | ReportError            
            }

            Write-Host "`r`n"
        }
        else
        {
            Write-Host "`r`n"
            Write-Host "`t`tPreferred Domain Controllers:"
            Write-Host "`t`t============================="

            #
            # Test connectivity to preferred domain controllers
            # Success : querying at least one PDC successfully
            # Failure : query fails for all PDCs
            #

            $isAllPDCsFailed = $true

            foreach ($preferredDC in $partition.PreferredDCs)
            {
                Write-Host "`t`tChecking connectivity to the preferred domain controller `"$preferredDC`"..."

                $isPreferredDCReachable = IsDomainReachable($preferredDC)

                if ($isPreferredDCReachable -eq $true)
                {
                    $isAllPDCsFailed = $false
                    "`t`tPreferred domain controller `"$preferredDC`" is reachable" | ReportOutput 
                    Write-Host "`r`n"
                    break
                }
                else
                {
                    "`t`tPreferred domain controller `"$preferredDC`" is not reachable" | ReportError
                    Write-Host "`r`n"
                }
            }

            if ($isAllPDCsFailed -eq $true)
            {
                WriteEventLog($EventIdDomainIsNotReachable)($EventMsgDomainIsNotReachable -f ($partition.Name))

                "`tDomain `"$($partition.Name)`" is not reachable" | ReportError
            }
            else
            {
                "`tDomain `"$($partition.Name)`" is reachable" | ReportOutput 
            }

            Write-Host "`r`n"
        }
    }
}

Function DiagnosePasswordSyncSingleObject
{
    param
    (
        [string]
        [parameter(mandatory=$false)]
        $ADConnectorName,

        [string]
        [parameter(mandatory=$false)]
        $DistinguishedName
    )

    if ([string]::IsNullOrEmpty($ADConnectorName) -and [string]::IsNullOrEmpty($DistinguishedName))
    {
        DiagnosePasswordSyncSingleObjectHelper
    }
    elseif ([string]::IsNullOrEmpty($ADConnectorName))
    {
        DiagnosePasswordSyncSingleObjectHelper -DistinguishedName $DistinguishedName
    }
    elseif ([string]::IsNullOrEmpty($DistinguishedName))
    {
        DiagnosePasswordSyncSingleObjectHelper -ADConnectorName $ADConnectorName
    }
    else
    {
        DiagnosePasswordSyncSingleObjectHelper -ADConnectorName $ADConnectorName -DistinguishedName $DistinguishedName
    }
    
    Write-Host "`r`n"

    if (-not $isNonInteractiveMode)
    {
        do
        {
            $answer = Read-Host "Did you find Password Hash Sync Single Object Diagnostics helpful? [y/n]"
        } 
        while(($answer -ne 'y') -and ($answer -ne 'Y') -and ($answer -ne 'n') -and ($answer -ne 'N'))

        WriteEventLog($EventIdIsPwdSyncSingleObjectDiagnosticsHelpful)($EventMsgIsPwdSyncSingleObjectDiagnosticsHelpful -f ($answer))
    }
}

#
# Diagnose password hash sync single object problems
#
# The object is specified by corresponding AD Connector Name and Distinguished Name of the object.
#
Function DiagnosePasswordSyncSingleObjectHelper
{
    param
    (
        [string]
        [parameter(mandatory=$false)]
        $ADConnectorName,

        [string]
        [parameter(mandatory=$false)]
        $DistinguishedName
    )

    WriteEventLog($EventIdSingleObjectDiagnosticsRun)($EventMsgSingleObjectDiagnosticsRun)

    Write-Host "`r`n"

    Write-Host "==========================================================================="
    Write-Host "= ="
    Write-Host "= Password Hash Synchronization Single Object Diagnostics ="
    Write-Host "= ="
    Write-Host "==========================================================================="

    $adConnectors = GetADConnectors
    if ($adConnectors -eq $null)
    {
        "No AD Connector is found. Password Hash Synchronization does not work in the absence of AD Connectors." | ReportError
        return        
    }

    if ([string]::IsNullOrEmpty($ADConnectorName) -or [string]::IsNullOrEmpty($DistinguishedName))
    {
        $adConnectorName = [string]::Empty
        $dn = [string]::Empty
    }
    else
    {
        $adConnectorName = $ADConnectorName
        $dn = $DistinguishedName
    }

    #
    # Get AD Connector Name as input
    # Prompt user again and provide a list of AD Connectors in case of wrong input
    #
    while ($true)
    {
        while ([string]::IsNullOrEmpty($adConnectorName))
        {
            Write-Host "`r`n"

            Write-Host "List of AD Connectors:"
            Write-Host "----------------------"

            foreach ($adConnector in $adConnectors)
            {
                Write-Host $adConnector.Name                
            }

            if ($adConnectors.length -eq 1)
            {
                $ADConnectorName = $adConnectors[0].Name
            }
            else
            {
                Write-Host "`r`n"

                $ADConnectorName = Read-Host "Please enter AD Connector Name"
            }
        }

        $adConnector = GetADConnectorByName($adConnectorName)

        if ($adConnector -eq $null)
        {
            ReportWarning "There is no AD Connector with name `"$adConnectorName`". Please try again!"
            $adConnectorName = [string]::Empty
            if ($isNonInteractiveMode)
            {
                return
            }
        }
        else
        {
            break
        }
    }

    # Get password hash sync configuration for the specified AD Connector
    $adConnectorPasswordSyncConfig = GetADConnectorPasswordSyncConfiguration($adConnector)

    # Check if password hash sync is enabled for the AD connector.
    if ($adConnectorPasswordSyncConfig.Enabled -eq $true)
    {
        "Password Hash Synchronization is enabled for AD Connector - $adConnectorName" | ReportOutput
    }
    else
    {
        WriteEventLog($EventIdSingleObjectConnectorDisabled)($EventMsgSingleObjectConnectorDisabled -f $adConnectorName)

        $errString = 
        "Password Hash Synchronization is disabled for AD Connector - $adConnectorName `n" +
        "Please enable Password Hash Synchronization from AADConnect Wizard in order to sync passwords"

        $errString | ReportError -PropertyName 'AD connector Password hash Sync' -PropertyValue 'Disabled'
        return
    }

    Write-Host "`r`n"

    while ($true)
    {
        while ([string]::IsNullOrEmpty($dn))
        {
            # Get AD connector space object Distinguished Name as input
            $dn = Read-Host "Please enter AD connector space object Distinguished Name"
        }
        
        #
        # Check if the object with the given Distinguished Name is in the AD connector space
        #
        $adCsObject = GetCSObject($adConnectorName)($dn)
    
        if ($adCsObject -eq $null)
        {
            "The object is not found in the AD connector space - $ADConnectorName" | ReportError
            Write-Host "`r`n"

            $dn = [string]::Empty

            if ($isNonInteractiveMode)
            {
                return
            }
            do
            {
                $confirmation = Read-Host "Would you like to try another Distinguished Name? [y/n]"

                if (($confirmation -eq 'n') -or ($confirmation -eq 'N'))
                {
                    Write-Host "`r`n"
                    return
                }
            } while(($confirmation -ne 'y') -or ($confirmation -ne 'Y'))

            Write-Host "`r`n"
        }
        else
        {
            Write-Host "`r`n"
            "The object is available in the AD connector space - $adConnectorName" | ReportOutput 
            break
        }
    }    
    
    #
    # Check if the object has a link to the metaverse
    #
    if ($adCsObject.IsConnector -eq $true)
    {
        "The object is a connector, it has a link to the metaverse" | ReportOutput 
    }
    else
    {
        WriteEventLog($EventIdSingleObjectDisconnector)($EventMsgSingleObjectDisconnector -f ($adConnectorName, $dn))

        "The object is a disconnector, it does not have a link to the metaverse" | ReportError
        return
    }
    
    #
    # Check if the object is synced to the AAD Connector
    #
    $aadConnector = GetAADConnector
    if ($aadConnector -eq $null)
    {
        "No AAD Connector is found. Password Hash Synchronization does not work in the absence of AAD Connector." | ReportError
        return
    }

    # Get metaverse object
    $mvObject = GetMVObjectByIdentifier($adCsObject.ConnectedMVObjectId)

    # Get target AAD connector space object identifier
    $aadCsObjectId = GetTargetCSObjectId($mvObject)($aadConnector.Identifier)
    if ($aadCsObjectId -eq $null)
    {
        WriteEventLog($EventIdSingleObjectNotSyncedToAADCS)($EventMsgSingleObjectNotSyncedToAADCS -f ($adConnectorName, $dn))

        "The object is not synced to the AAD connector space" | ReportError
        return
    }

    # Get target AAD connector space object
    $aadCsObject = GetCSObjectByIdentifier($aadCsObjectId)
    
    if ($aadCsObject -eq $null)
    {
        WriteEventLog($EventIdSingleObjectNotSyncedToAADCS)($EventMsgSingleObjectNotSyncedToAADCS -f ($adConnectorName, $dn))

        "The object is not synced to the AAD connector space" | ReportError
        return
    }
    else
    {
        "The object is synced to the AAD connector space" | ReportOutput 
    }

    Write-Host "`r`n"

    #
    # Check lineage of AD connector space object
    #
    $adCsObjectPasswordSyncRule = GetCsObjectPasswordSyncRule($adCsObject)

    if ($adCsObjectPasswordSyncRule -eq $null)
    {
        WriteEventLog($EventIdSingleObjectNoADPwdSyncRule)($EventMsgSingleObjectNoADPwdSyncRule -f ($adConnectorName, $dn))

        "There is no Password Hash Synchronization rule for AD connector space object" | ReportError

        # Check if AD connector space object has synchronization error
        if ($adCsObject.HasSyncError -eq $true)
        {
            "The AD connector space object has synchronization error" | ReportError 
        }

        "Please check synchronization rules or see: https://go.microsoft.com/fwlink/?linkid=847233" | Write-Host -fore Red

        return    
    }
    else
    {
        "Password Hash Synchronization rule is found for AD connector space object" | ReportOutput 
        $adCsObjectPasswordSyncRule | select Name, Direction, LinkType, EnablePasswordSync | ft -AutoSize | Out-String | Write-Host
    }
    
    #
    # Check lineage of AAD connector space object
    #
    $aadCsObjectPasswordSyncRule = GetCsObjectPasswordSyncRule($aadCsObject)

    if ($aadCsObjectPasswordSyncRule -eq $null)
    {
        WriteEventLog($EventIdSingleObjectNoAADPwdSyncRule)($EventMsgSingleObjectNoAADPwdSyncRule -f ($adConnectorName, $dn))

        "There is no Password Hash Synchronization rule for target AAD connector space object" | ReportError

        # Check if target AAD connector space object has synchronization error
        if ($aadCsObject.HasSyncError -eq $true)
        {
            "The target AAD connector space object has synchronization error" | ReportError
        }

        "Please check synchronization rules or see: https://go.microsoft.com/fwlink/?linkid=847233" | Write-Host -fore Red

        return
    }
    else
    {
        "Password Hash Synchronization rule is found for target AAD connector space object" | ReportOutput 
        $aadCsObjectPasswordSyncRule | select Name, Direction, LinkType, EnablePasswordSync | ft -AutoSize | Out-String | Write-Host    
    }

    #
    # Check CS Object Log Entry
    #
    $logEntryCount = 1
    $adCsObjectLogEntries = GetCSObjectLog($adCsObject)($logEntryCount)

    if ($adCsObjectLogEntries -eq $null)
    {
        WriteEventLog($EventIdSingleObjectNoPwdHistory)($EventMsgSingleObjectNoPwdHistory -f ($adConnectorName, $dn))

        $warningString = 
        "Password Hash Synchronization agent does not have any password change history for the specified object `n" +
        "Password change history is purged once in a week."
        
        $warningString | ReportWarning

        Write-Host "`r`n"
        return
    }

    $adCsObjectLatestLogEntry = $adCsObjectLogEntries[0]
    $adCsObjectLatestLogEntryDateTime = "$($adCsObjectLatestLogEntry.TimeStamp) UTC"


    $resultString =
    "Password Hash Synchronization agent read the last password change for the specified object at: $adCsObjectLatestLogEntryDateTime `n"  +
    "The result of the Password Hash Synchronization attempt was: `n"
    ReportOutput -PropertyName 'Last password change for the specified object at' -PropertyValue "$adCsObjectLatestLogEntryDateTime"


    if (-not $isNonInteractiveMode)
    {
        Write-Host $resultString
        $resultString = ""
    }

    if ($adCsObjectLatestLogEntry.Status -eq "Success")
    {
        WriteEventLog($EventIdSingleObjectSuccess)($EventMsgSingleObjectSuccess -f ($adConnectorName, $dn, $adCsObjectLatestLogEntryDateTime))

        $resultString + "Password hash is synchronized successfully" | ReportOutput 
        ReportOutput -PropertyName 'Last password change status for the specified object' -PropertyValue "Succeeded"
    }
    elseif ($adCsObjectLatestLogEntry.Status -eq "FilteredByTarget")
    {
        WriteEventLog($EventIdSingleObjectFilteredByTarget)($EventMsgSingleObjectFilteredByTarget -f ($adConnectorName, $dn, $adCsObjectLatestLogEntryDateTime))

        $resultString + 
        "Password is set with user must change password at next logon option. Temporary passwords are not supposed to be synchronized. `n" +
        "Please see: https://go.microsoft.com/fwlink/?linkid=847233" | ReportError

    }
    elseif ($adCsObjectLatestLogEntry.Status -eq "TargetNotExportedToDirectory")
    {
        WriteEventLog($EventIdSingleObjectNotExported)($EventMsgSingleObjectNotExported -f ($adConnectorName, $dn, $adCsObjectLatestLogEntryDateTime))

        $resultString + "The object in the AAD connector space has not yet been exported. The password hash is not supposed to be synchronized." | ReportError

        # Check if target AAD connector space object has export error
        if ($aadCsObject.HasExportError -eq $true)
        {
            "The target AAD connector space object has export error" | ReportError
        }

        "Please see: https://go.microsoft.com/fwlink/?linkid=847233" | Write-Host -fore Red
    }
    elseif ($adCsObjectLatestLogEntry.Status -eq "NoTargetConnection")
    {
        WriteEventLog($EventIdSingleObjectNoTargetConnection)($EventMsgSingleObjectNoTargetConnection -f ($adConnectorName, $dn, $adCsObjectLatestLogEntryDateTime))
        
        $resultString + "The object is not synced to the AAD connector space or Password Hash Synchronization rule(s) are not available" | ReportError
        "Please see: https://go.microsoft.com/fwlink/?linkid=847233" | Write-Host -fore Red
    }
    else
    {
        WriteEventLog($EventIdSingleObjectOtherFailure)($EventMsgSingleObjectOtherFailure -f ($adConnectorName, $dn, $adCsObjectLatestLogEntryDateTime, $adCsObjectLatestLogEntry.Status))

        $resultString + "$($adCsObjectLatestLogEntry.Status)" | ReportError
    }

    Write-Host "`r`n"
}

Function PromptPasswordSyncSingleObjectDiagnostics
{
    #
    # Prompt user to diagnose single object issues
    #
    do
    {
        $confirmation = Read-Host "Would you like to diagnose single object issues? [y/n]"

        if (($confirmation -eq 'n') -or ($confirmation -eq 'N'))
        {
            return
        }
    } while(($confirmation -ne 'y') -or ($confirmation -ne 'Y'))
    
    #
    # Diagnose password hash synchronization single object issues
    #
    DiagnosePasswordSyncSingleObject
}

Function SynchronizeSingleObjectPassword
{
    Write-Host "`r`n"

    Write-Host "==========================================================================="
    Write-Host "= ="
    Write-Host "= Single Object Password Hash Synchronization Utility ="
    Write-Host "= ="
    Write-Host "==========================================================================="

    Write-Host "`r`n"

    "This utility will attempt to synchronize the current password hash stored in the on-premises Active Directory for the specified user account." | Write-Host -fore Cyan

    # Check if Staging Mode is enabled
    $isStagingModeEnabled = IsStagingModeEnabled
    if ($isStagingModeEnabled -eq $true)
    {
        Write-Host "`r`n"
        "Staging mode is enabled. Password Hash Synchronization does not work when staging mode is enabled." | Write-Host -fore Red
        return
    }

    # Get AD Connectors
    $adConnectors = GetADConnectors
    if ($adConnectors -eq $null)
    {
        Write-Host "`r`n"
        "No AD Connector is found. Password Hash Synchronization does not work in the absence of AD Connectors." | Write-Host -fore Red
        return
    }

    # Get AAD Connector
    $aadConnector = GetAADConnector
    if ($aadConnector -eq $null)
    {
        Write-Host "`r`n"
        "No AAD Connector is found. Password Hash Synchronization does not work in the absence of AAD Connector." | Write-Host -fore Red
        return
    }

    "Please specify the user account by providing the regarding AD Connector Name and DistinguishedName." | Write-Host -fore Cyan

    #
    # Get AD Connector Name as input
    # Prompt user again and provide a list of AD Connectors in case of wrong input
    #
    $adConnectorName = $null
    $adConnector = $null

    while ($true)
    {
        while ([string]::IsNullOrEmpty($adConnectorName))
        {
            Write-Host "`r`n"

            Write-Host "List of AD Connectors:"
            Write-Host "----------------------"

            foreach ($adConnector in $adConnectors)
            {
                Write-Host $adConnector.Name                
            }

            if ($adConnectors.length -eq 1)
            {
                $adConnectorName = $adConnectors[0].Name
            }
            else
            {
                Write-Host "`r`n"

                $adConnectorName = Read-Host "Please enter AD Connector Name"
            }
        }
        
        $adConnector = GetADConnectorByName($adConnectorName)

        if ($adConnector -eq $null)
        {
            Write-Warning "There is no AD Connector with name `"$adConnectorName`". Please try again!"

            $adConnectorName = [string]::Empty
        }
        else
        {
            break
        }
    }

    # Get password hash sync configuration for the specified AD Connector
    $adConnectorPasswordSyncConfig = GetADConnectorPasswordSyncConfiguration($adConnector)

    # Check if password hash sync is enabled for the AD connector.
    if ($adConnectorPasswordSyncConfig.Enabled -eq $true)
    {
        "Password Hash Synchronization is enabled for AD Connector - $adConnectorName" | Write-Host -fore Green
    }
    else
    {
        "Password Hash Synchronization is disabled for AD Connector - $adConnectorName" | Write-Host -fore Red
        "Please enable Password Hash Synchronization from AADConnect Wizard in order to synchronize passwords" | Write-Host -fore Red
        return
    }

    Write-Host "`r`n"
    $dn = $null
    $adCsObject = $null

    while ($true)
    {
        while ([string]::IsNullOrEmpty($dn))
        {
            # Get AD connector space object Distinguished Name as input
            $dn = Read-Host "Please enter AD connector space object Distinguished Name"
        }
        
        #
        # Check if the object with the given Distinguished Name is in the AD connector space
        #
        $adCsObject = GetCSObject($adConnectorName)($dn)
    
        if ($adCsObject -eq $null)
        {
            "The object is not found in the AD connector space - $ADConnectorName" | Write-Host -fore Red
            Write-Host "`r`n"

            $dn = [string]::Empty

            do
            {
                $confirmation = Read-Host "Would you like to try another Distinguished Name? [y/n]"

                if (($confirmation -eq 'n') -or ($confirmation -eq 'N'))
                {
                    Write-Host "`r`n"
                    return
                }
            } while(($confirmation -ne 'y') -and ($confirmation -ne 'Y'))

            Write-Host "`r`n"
        }
        else
        {
            break
        }
    }

    if ($adCsObject.ObjectType -ne "user")
    {
        "Connector space object type is $($adCsObject.ObjectType). Password Hash Synchronization is only supported for the object type user in Active Directory." | Write-Host -fore Red
        return
    }

    $singleObjectPasswordSyncResult = Invoke-ADSyncCSObjectPasswordHashSync -CsObject $adCsObject
    
    if ($singleObjectPasswordSyncResult.Contains("success"))
    {
        $singleObjectPasswordSyncResult | Write-Host -fore Green
    }
    else
    {
        $singleObjectPasswordSyncResult | Write-Host -fore Red
    }
}

Function DiagnosePasswordHashSyncNonInteractiveMode
{
    param
    (
        [string]
        [parameter(mandatory=$true)]
        $ADConnectorName
    )

    $timezone = [TimeZoneInfo]::Local
    ReportOutput -PropertyName 'Sync Server TimeZone' -PropertyValue $timezone.DisplayName

    DiagnosePasswordHashSyncHelper -ADConnectorName $ADConnectorName
}

Function DiagnosePasswordHashSync
{
    DiagnosePasswordHashSyncHelper

    Write-Host "`r`n"

    do
    {
        $answer = Read-Host "Did you find Password Hash Sync General Diagnostics helpful? [y/n]"
    } 
    while(($answer -ne 'y') -and ($answer -ne 'Y') -and ($answer -ne 'n') -and ($answer -ne 'N'))

    WriteEventLog($EventIdIsPwdSyncGeneralDiagnosticsHelpful)($EventMsgIsPwdSyncGeneralDiagnosticsHelpful -f $answer)
}

#
# Diagnose Password Hash Sync - composed of 3 parts
#
# + Password hash sync configurations
# + Connectivity to each AD Forest
# + Single Object Issues
#
# This function is the point of entry for diagnosing password hash sync issues
#
Function DiagnosePasswordHashSyncHelper
{
    param
    (
        [string]
        [parameter(mandatory=$false)]
        $ADConnectorName = [string]::Empty
    )

    WriteEventLog($EventIdPwdSyncTroubleshootingRun)($EventMsgPwdSyncTroubleshootingRun)

    # Check if Staging Mode is enabled
    $isStagingModeEnabled = IsStagingModeEnabled
    if ($isStagingModeEnabled -eq $true)
    {
        WriteEventLog($EventIdStagingModeEnabled)($EventMsgStagingModeEnabled)

        "Staging mode is enabled. Password Hash Synchronization does not work when staging mode is enabled." | ReportError
        return
    }

    # Get AD Connectors

    if (-not [string]::IsNullOrEmpty($ADConnectorName))
    {
        $adConnectors = GetADConnectorByName -ADConnectorName $ADConnectorName
    }
    else
    {
        $adConnectors = GetADConnectors
    }

    if ($adConnectors -eq $null)
    {
        "No AD Connector is found. Password Hash Synchronization does not work in the absence of AD Connectors." | ReportError
        return
    }

    # Get AAD Connector
    $aadConnector = GetAADConnector
    if ($aadConnector -eq $null)
    {
        "No AAD Connector is found. Password Hash Synchronization does not work in the absence of AAD Connector." | ReportError
        return
    }

    # AAD Tenant Name
    $aadTenantName = GetAADTenantName($aadConnector)

    Write-Host "`r`n"

    Write-Host "========================================================================"
    Write-Host "= ="
    Write-Host "= Password Hash Synchronization General Diagnostics ="
    Write-Host "= ="
    Write-Host "========================================================================"

    Write-Host "`r`n"

    Write-Host "AAD Tenant - $aadTenantName"

    if (-not $isNonInteractiveMode)
    {
        # Password Hash Sync Cloud Configuration
        $passwordHashSyncCloudConfiguration = GetPasswordHashSyncCloudConfiguration($aadConnector)
        if ($passwordHashSyncCloudConfiguration -eq $true)
        {
            "Password Hash Synchronization cloud configuration is enabled" | ReportOutput
        }
        else
        {
            "Password Hash Synchronization cloud configuration is disabled" | ReportError
        }
    }


    Write-Host "`r`n"

    #
    # Password Hash Sync Local Configuration
    #
    # Enabled - there is one or more AD Connectors enabled for password hash sync
    # Disabled - all AD Connectors are disabled for password hash sync
    #
    $passwordHashSyncLocalConfiguration = $false

    # Check Password Hash Sync AWS API Call Failures
    CheckLatestPasswordSyncAWSCallFailureEvents

    Write-Host "`r`n"

    #
    # Check if password hash sync agent continuously fails to compute MD5 decryption key.
    #
    IsPersistentMD5Failure

    #
    # Check if verbose logging for password hash sync is turned on
    #
    CheckVerboseLoggingForPHS

    #
    # Per AD Connector Password Hash Sync Status
    #
    foreach ($adConnector in $adConnectors)
    {
        # AD Connector password hash sync configuration
        $adConnectorPasswordSyncConfig = GetADConnectorPasswordSyncConfiguration($adConnector)
        
        # AD Connector latest heartbeat (ping) event in the last 3 hours
        $adConnectorLatestPingEvent = GetADConnectorLatestPingEvent($adConnector)

        Write-Host "`r`n"
        Write-Host "AD Connector - $($adConnector.Name)"

        if ($adConnectorPasswordSyncConfig.Enabled -eq $true)
        {
            "Password Hash Synchronization is enabled" | ReportOutput 

            if ($adConnectorLatestPingEvent -eq $null)
            {
                WriteEventLog($EventIdConnectorNoHeartBeat)($EventMsgConnectorNoHeartBeat -f ($adConnector.Name))

                #
                # Check if password hash sync agent continuously gets health task failures
                #
                # AWS is pinged through password hash sync health task. The failures in this category are the ones
                # prior to making an AWS API call for the ping.
                #
                IsPersistentHealthTaskFailure($adConnector)

                # AD Connector latest password hash sync activity event in the last 2 hours
                $adConnectorPasswordHashSyncLatestActivityEvent = GetADConnectorPasswordHashSyncLatestActivityEvent($adConnector)

                #
                # Password Hash Synchronization events 609, 610 and 615 are about stopping the password hash sync channel for the AD Connector.
                #
                # 609 and 610 indicates that there is an intentional attempt to stop the channel. When the channel is stopped intentionally,
                # AD Connector configuration is modified as password hash sync disabled. Therefore, it is NOT very likely to see these events
                # here as there is already a check for the AD Connector password hash sync configuration above.
                #
                # 615 indicates that the channel stopped due to an exception.
                #
                if ($adConnectorPasswordHashSyncLatestActivityEvent -eq $null -or 
                    $adConnectorPasswordHashSyncLatestActivityEvent.InstanceId -eq 609 -or
                    $adConnectorPasswordHashSyncLatestActivityEvent.InstanceId -eq 610 -or
                    $adConnectorPasswordHashSyncLatestActivityEvent.InstanceId -eq 615)
                {
                    WriteEventLog($EventIdConnectorPwdSyncStopped)($EventMsgConnectorPwdSyncStopped -f ($adConnector.Name))

                    "Password Hash Synchronization is NOT running for AD Connector: $($adConnector.Name)" | ReportError
                    Write-Host "`r`n"

                    if (-not $isNonInteractiveMode)
                    {
                        do
                        {
                            $confirmation = Read-Host "Would you like to RESTART password hash synchronization for AD Connector: $($adConnector.Name)? [y/n]"

                            if (($confirmation -eq 'y') -or ($confirmation -eq 'Y'))
                            {
                                Write-Host "Restarting..."
                                RestartADConnectorPasswordHashSyncChannel($adConnector)($aadConnector)

                                Write-Host "`r`n"
                                break
                            }
                        } while(($confirmation -ne 'n') -or ($confirmation -ne 'N'))
                    }
                }
                else
                {
                    WriteEventLog($EventIdPwdSyncActivityWithoutHeartbeat)($EventMsgPwdSyncActivityWithoutHeartbeat -f ($adConnector.Name))
                }
            }
            else
            {
                $adConnectorLatestPingDateTime = GetDateTimeLocaleEnUs($adConnectorLatestPingEvent.TimeGenerated)
                "Latest Password Hash Synchronization heartbeat is detected at: $adConnectorLatestPingDateTime UTC" | 
                ReportOutput -PropertyName 'Latest PHS heartbeat detected at' -PropertyValue "$adConnectorLatestPingDateTime UTC"
                
            }
        }
        else
        {
            "Password Hash Synchronization is disabled" | ReportError
            continue
            Write-Host "`r`n"
        }

        $passwordHashSyncLocalConfiguration = $passwordHashSyncLocalConfiguration -or $adConnectorPasswordSyncConfig.Enabled

        # Diagnose connectivity for each AD forest
        DiagnoseADConnectivity($adConnector)
    }

    # Check if password hash sync local and cloud configurations are same. Skip comparing configuration in Non-Interactive mode. Since ASC already has config values.
    if ((-not $isNonInteractiveMode) -and ($passwordHashSyncLocalConfiguration -ne $passwordHashSyncCloudConfiguration))
    {
        WriteEventLog($EventIdPwdSyncLocalAndCloudConfigDifferent)($EventMsgPwdSyncLocalAndCloudConfigDifferent -f ($passwordHashSyncLocalConfiguration, $passwordHashSyncCloudConfiguration))

        if ($passwordHashSyncLocalConfiguration -eq $true -and $passwordHashSyncCloudConfiguration -eq $false)
        {
            WriteEventLog($EventIdPwdSyncLocalEnabledAndCloudDisabled)($EventMsgPwdSyncLocalAndCloudConfigDifferent -f ($passwordHashSyncLocalConfiguration, $passwordHashSyncCloudConfiguration))
        }
        elseif ($passwordHashSyncLocalConfiguration -eq $false -and $passwordHashSyncCloudConfiguration -eq $true)
        {
            WriteEventLog($EventIdPwdSyncLocalDisabledAndCloudEnabled)($EventMsgPwdSyncLocalAndCloudConfigDifferent -f ($passwordHashSyncLocalConfiguration, $passwordHashSyncCloudConfiguration))
        }
        elseif ($passwordHashSyncCloudConfiguration -eq $null)
        {
            WriteEventLog($EventIdPwdSyncCloudConfigNull)($EventMsgPwdSyncLocalAndCloudConfigDifferent -f ($passwordHashSyncLocalConfiguration, $passwordHashSyncCloudConfiguration))
        }

        Write-Host "`r`n"
        "Password Hash Synchronization local and cloud configurations are different" | ReportError
        return
    }
    elseif ($passwordHashSyncLocalConfiguration -eq $false)
    {
        WriteEventLog($EventIdPwdSyncLocalConfigDisabled)($EventMsgPwdSyncLocalConfigDisabled)

        Write-Host "`r`n"
        ReportWarning "Password Hash Synchronization is disabled for all AD Connectors"
        ReportWarning "In order to synchronize passwords, you need to enable Password Hash Synchronization from AADConnect Wizard"
        return
    }

    Write-Host "`r`n"
}
# SIG # Begin signature block
# MIInnwYJKoZIhvcNAQcCoIInkDCCJ4wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCPmlO0w0yrXsnN
# UvBYy1C7D0dCdwWjqZVKb/1e1lJfzqCCDYIwggYAMIID6KADAgECAhMzAAADXJXz
# SFtKBGrPAAAAAANcMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwNDA2MTgyOTIyWhcNMjQwNDAyMTgyOTIyWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDijA1UCC84R0x+9Vr/vQhPNbfvIOBFfymE+kuP+nho3ixnjyv6vdnUpgmm6RT/
# pL9cXL27zmgVMw7ivmLjR5dIm6qlovdrc5QRrkewnuQHnvhVnLm+pLyIiWp6Tow3
# ZrkoiVdip47m+pOBYlw/vrkb8Pju4XdA48U8okWmqTId2CbZTd8yZbwdHb8lPviE
# NMKzQ2bAjytWVEp3y74xc8E4P6hdBRynKGF6vvS6sGB9tBrvu4n9mn7M99rp//7k
# ku5t/q3bbMjg/6L6mDePok6Ipb22+9Fzpq5sy+CkJmvCNGPo9U8fA152JPrt14uJ
# ffVvbY5i9jrGQTfV+UAQ8ncPAgMBAAGjggF/MIIBezArBgNVHSUEJDAiBgorBgEE
# AYI3TBMBBgorBgEEAYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUXgIsrR+tkOQ8
# 10ekOnvvfQDgTHAwRQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEWMBQGA1UEBRMNMjMzMTEwKzUwMDg2ODAfBgNVHSMEGDAWgBRI
# bmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEt
# MDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBABIm
# T2UTYlls5t6i5kWaqI7sEfIKgNquF8Ex9yMEz+QMmc2FjaIF/HQQdpJZaEtDM1Xm
# 07VD4JvNJEplZ91A4SIxjHzqgLegfkyc384P7Nn+SJL3XK2FK+VAFxdvZNXcrkt2
# WoAtKo0PclJOmHheHImWSqfCxRispYkKT9w7J/84fidQxSj83NPqoCfUmcy3bWKY
# jRZ6PPDXlXERRvl825dXOfmCKGYJXHKyOEcU8/6djs7TDyK0eH9ss4G9mjPnVZzq
# Gi/qxxtbddZtkREDd0Acdj947/BTwsYLuQPz7SNNUAmlZOvWALPU7OOVQlEZzO8u
# Ec+QH24nep/yhKvFYp4sHtxUKm1ZPV4xdArhzxJGo48Be74kxL7q2AlTyValLV98
# u3FY07rNo4Xg9PMHC6sEAb0tSplojOHFtGtNb0r+sioSttvd8IyaMSfCPwhUxp+B
# Td0exzQ1KnRSBOZpxZ8h0HmOlMJOInwFqrCvn5IjrSdjxKa/PzOTFPIYAfMZ4hJn
# uKu15EUuv/f0Tmgrlfw+cC0HCz/5WnpWiFso2IPHZyfdbbOXO2EZ9gzB1wmNkbBz
# hj8hFyImnycY+94Eo2GLavVTtgBiCcG1ILyQabKDbL7Vh/OearAxcRAmcuVAha07
# WiQx2aLghOSaZzKFOx44LmwUxRuaJ4vO/PRZ7EzAMIIHejCCBWKgAwIBAgIKYQ6Q
# 0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5
# WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQD
# Ex9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4
# BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe
# 0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato
# 88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v
# ++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDst
# rjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN
# 91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4ji
# JV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmh
# D+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbi
# wZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8Hh
# hUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaI
# jAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTl
# UAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV
# HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQF
# TuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29m
# dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNf
# MjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5t
# aWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNf
# MjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcC
# ARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnlj
# cHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5
# AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oal
# mOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0ep
# o/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1
# HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtY
# SWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInW
# H8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZ
# iWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMd
# YzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7f
# QccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKf
# enoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOpp
# O6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZO
# SEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXMwghlvAgEBMIGVMH4xCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANclfNIW0oEas8AAAAAA1ww
# DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJOT5BQ9
# XdLBvSsKgM11CqOQqP/pr+V5nDPYxjuX7gsVMEIGCisGAQQBgjcCAQwxNDAyoBSA
# EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w
# DQYJKoZIhvcNAQEBBQAEggEA4Jw2Ln9X6pvUZ/+z3WIjt6B6oJRPuvnesXv7rlXh
# 9jkawE/Do4G4S71STYfHT/O4EkJ/EJxyA+A60Dtj4oOGEvbh/HvCIGH/6WWk2dRq
# 4/9gUaib8MOQ7qkbs3qW2j1q2tautzV078F/FejUVY4FkHh2kJ6/a6BuKwknOWCF
# RO9XcLhfL1JPKj9w5Ll1LTzucNSRs9ZcEQXIV6FtJ0P38mQ98TKmFJ7Ova0EaXAl
# aq5/9mHsLfUnAeHF6EOmu6xHwbldIVvAKbP+3YuIILYr8/TcB/8m7szV+ODeyt5U
# 9FWAZwlIvN15Fo6K/2xAaeF2EK+yzIlq8WYtuCD3wKMcB6GCFv0wghb5BgorBgEE
# AYI3AwMBMYIW6TCCFuUGCSqGSIb3DQEHAqCCFtYwghbSAgEDMQ8wDQYJYIZIAWUD
# BAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGEWQoD
# ATAxMA0GCWCGSAFlAwQCAQUABCBepW6FOqRkMCDcx/zIY7n3VZNoF1UCs5VKT/iR
# GQKf6wIGZFzVBrDDGBMyMDIzMDUxNzIyNTUzMC44NjFaMASAAgH0oIHQpIHNMIHK
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN
# aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNT
# IEVTTjpERDhDLUUzMzctMkZBRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgU2VydmljZaCCEVQwggcMMIIE9KADAgECAhMzAAABxQPNzSGh9O85AAEAAAHF
# MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4X
# DTIyMTEwNDE5MDEzMloXDTI0MDIwMjE5MDEzMlowgcoxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNh
# IE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkREOEMtRTMzNy0y
# RkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq0hds70eX23J7pappaKXRhz+TT7J
# J3OvVf3+N8fNpxRs5jY4hEv3BV/w5EWXbZdO4m3xj01lTI/xDkq+ytjuiPe8xGXs
# ZxDntv7L1EzMd5jISqJ+eYu8kgV056mqs8dBo55xZPPPcxf5u19zn04aMQF5PXV/
# C4ZLSjFa9IFNcribdOm3lGW1rQRFa2jUsup6gv634q5UwH09WGGu0z89RbtbyM55
# vmBgWV8ed6bZCZrcoYIjML8FRTvGlznqm6HtwZdXMwKHT3a/kLUSPiGAsrIgEzz7
# NpBpeOsgs9TrwyWTZBNbBwyIACmQ34j+uR4et2hZk+NH49KhEJyYD2+dOIaDGB2E
# UNFSYcy1MkgtZt1eRqBB0m+YPYz7HjocPykKYNQZ7Tv+zglOffCiax1jOb0u6IYC
# 5X1Jr8AwTcsaDyu3qAhx8cFQN9DDgiVZw+URFZ8oyoDk6sIV1nx5zZLy+hNtakeP
# X9S7Y8n1qWfAjoXPE6K0/dbTw87EOJL/BlJGcKoFTytr0zPg/MNJSb6f2a/wDkXo
# GCGWJiQrGTxjOP+R96/nIIG05eE1Lpky2FOdYMPB4DhW7tBdZautepTTuShmgn+G
# KER8AoA1gSSk1EC5ZX4cppVngJpblMBu8r/tChfHVdXviY6hDShHwQCmZqZebgSY
# HnHl4urE+4K6ZC8CAwEAAaOCATYwggEyMB0GA1UdDgQWBBRU6rs4v1mxNYG/rtpL
# wrVwek0FazAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8E
# WDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9N
# aWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYB
# BQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
# cGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEw
# KDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqG
# SIb3DQEBCwUAA4ICAQCMqN58frMHOScciK+Cdnr6dK8fTsgQDeZ9bvQjCuxNIJZJ
# 92+xpeKRCf3Xq47qdRykkKUnZC6dHhLwt1fhwyiy/LfdVQ9yf1hYZ/RpTS+z0hna
# oK+P/IDAiUNm32NXLhDBu0P4Sb/uCV4jOuNUcmJhppBQgQVhFx/57JYk1LCdjIee
# //GrcfbkQtiYob9Oa93DSjbsD1jqaicEnkclUN/mEm9ZsnCnA1+/OQDp/8Q4cPfH
# 94LM4J6X0NtNBeVywvWH0wuMaOJzHgDLCeJUkFE9HE8sBDVedmj6zPJAI+7ozLjY
# qw7i4RFbiStfWZSGjwt+lLJQZRWUCcT3aHYvTo1YWDZskohWg77w9fF2QbiO9Dfn
# qoZ7QozHi7RiPpbjgkJMAhrhpeTf/at2e9+HYkKObUmgPArH1Wjivwm1d7PYWsar
# L7u5qZuk36Gb1mETS1oA2XX3+C3rgtzRohP89qZVf79lVvjmg34NtICK/pMk99SB
# utghtipFSMQdbXUnS2oeLt9cKuv1MJu+gJ83qXTNkQ2QqhxtNRvbE9QqmqJQw5VW
# /4SZze1pPXxyOTO5yDq+iRIUubqeQzmUcCkiyNuCLHWh8OLCI5mIOC1iLtVDf2lw
# 9eWropwu5SDJtT/ZwqIU1qb2U+NjkNcj1hbODBRELaTTWd91RJiUI9ncJkGg/jCC
# B3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAw
# gYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMT
# KU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIx
# MDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57Ry
# IQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VT
# cVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhx
# XFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQ
# HJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1
# KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s
# 4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUg
# fX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3
# Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je
# 1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUY
# hEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUY
# P3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGC
# NxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4w
# HQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYB
# BAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcD
# CDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0T
# AQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNV
# HR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w
# cm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEE
# TjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2Nl
# cnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOC
# AgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/a
# ZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp
# 4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq
# 95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qB
# woEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG
# +jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3B
# FARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77
# IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJ
# fn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K
# 6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDx
# yKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLLMIICNAIBATCB
# +KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEl
# MCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMd
# VGhhbGVzIFRTUyBFU046REQ4Qy1FMzM3LTJGQUUxJTAjBgNVBAMTHE1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVACEAGvYXZJK7cUo6
# 2+LvEYQEx7/noIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
# DQYJKoZIhvcNAQEFBQACBQDoDzt5MCIYDzIwMjMwNTE3MTkzOTM3WhgPMjAyMzA1
# MTgxOTM5MzdaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOgPO3kCAQAwBwIBAAIC
# JlowBwIBAAICVCswCgIFAOgQjPkCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYB
# BAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOB
# gQAod9dxSGcVPcJFw9uXD6Sgl+ckUKHTqRBX8yFEDvoDpMNdHTdG/+r1rhX6itQ0
# EiM5X5JQ5B5G3lKxEuJaCoetNdEKCs2WYYJBU7Uu4dPG2ctG3mUk9QX3KtsYns9j
# gJao5Gad+72HZAglNQJ7iOtKlHjutjbT3UJhzIV4OfMUuzGCBA0wggQJAgEBMIGT
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABxQPNzSGh9O85AAEA
# AAHFMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ
# AQQwLwYJKoZIhvcNAQkEMSIEIHQ0ccNGy6ddESWBOlxM5nVN8196aVjemSYT8MLu
# TA5bMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgGQGxkfYkd0wK+V09wO0s
# O+sm8gAMyj5EuKPqvNQ/fLEwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMAITMwAAAcUDzc0hofTvOQABAAABxTAiBCBwYugKFVKYVjoe+yb+rUyO
# cC01pxRTB+vR3wl7l3zeojANBgkqhkiG9w0BAQsFAASCAgCBEAFvyy9DsK5kIZt9
# ck1McyML+AdixyNd1Q37K9lMF1b6ngVsSBETnj5MEFTQux5a993bM0M1oPMca+ol
# s89FERnq7O8X3uQ2wG8VAsMpnHOX6XfK1EXxpE2nsx/LpmPTVVF0a0RDwvr1+oxN
# GYL3sD0d2dQ9Fk3lItWLznGcM9mIpHQ+mY/dSFnwjf+2zG0rehx7lLYXKGfIlPEq
# iIToU6Uv3U0xKcj7rdIL63jYmxcTQWgzq9ZcvHTaqSGV7pbG7f5bmKPQV9gr9hiq
# 10Ngv/7ubknVHDUaK9dbV4zpONUPWeSkV4dpvBkmU3Uwx7TOqVHzyTopVVkaOeuD
# Hq5GBjqh5f0ef6wMCRNrV2xkR+sL461FjjuTK9VcS8Yn75ZNsAwKfMWTRIx5bjBT
# LpPIu0NF+usoQL8XXDYvKcG0VDgMSX70tePaH+K+8+fxqoJUh0MWfffkgTIlZS+E
# UDT00sUv7xqCYeoqORL8xNxo7LxqUIYTUaX5QMuTMHuOq2tbTScye45ygHN0ebot
# WAU6hHdymUQ65jxACcz1/a7cnQehtsEwbmg92v7wI9XOn+aMd7rkvspJsQGPzRKz
# KzTlNoiqY3emOOZP7S6Sgt0GhXhJKAvLvI9tIjqUW5nKDPFoAMo7/L+surIoGEVS
# N6J0JWvgxhJkJDpLzulcG15E6g==
# SIG # End signature block