AzStackHciExternalActiveDirectory/AzStackHci.ExternalActiveDirectory.Tests.psm1

Import-LocalizedData -BindingVariable lcAdTxt -FileName AzStackHci.ExternalActiveDirectory.Strings.psd1

class ExternalADTest
{
    [string]$TestName
    [scriptblock]$ExecutionBlock
}

$ExternalAdTestInitializors = @(
)

$ExternalAdTests = @(
    <# Can't execute this test during deployment as Get-KdsRootKey will try to access the DVM KDS and come up with an empty value
    (New-Object -Type ExternalADTest -Property @{
        TestName = "KdsRootKeyExists"
        ExecutionBlock = {
            Param ([hashtable]$testContext)
 
            $dcName = $null
            $KdsRootKey = $null
            $accessDenied = $false
            try
            {
                # Must use the server name and credentials that are passed in if they exist
                $getDomainControllerParams = @{}
                if ($testContext["AdServer"] -and $testContext["AdCredentials"])
                {
                    $getDomainControllerParams += @{Server = $testContext["AdServer"]}
                    $getDomainControllerParams += @{Credential = $testContext["AdCredentials"]}
                }
                else
                {
                    $getDomainControllerParams += @{DomainName = $testContext["DomainFQDN"]}
                    $getDomainControllerParams += @{MinimumDirectoryServiceVersion = "Windows2012"}
                    $getDomainControllerParams += @{NextClosestSite = $true}
                    $getDomainControllerParams += @{Discover = $true}
                }
                $adDomainController = Get-ADDomainController @getDomainControllerParams
                $dcName = "$($adDomainController.Name).$($adDomainController.Domain)"
 
                # This cmdlet doesn't take a server name or credentials, so it may fail when not run from a domain-joined machine
                $KdsRootKey = $null
                try
                {
                    $KdsRootKey = Get-KdsRootKey
                }
                catch
                {
                    $accessDenied = $true
                }
 
                if($KdsRootKey)
                {
                    # make sure it is effective at least 10 hours ago
                    if(((Get-Date) - $KdsRootKey.EffectiveTime).TotalHours -lt 10)
                    {
                        $KdsRootKey = $null
                    }
                }
            }
            catch {}
 
            $rootKeyStatus = if ($dcName -and $KdsRootKey) { 'SUCCESS' } else { 'FAILURE' }
            if ($accessDenied)
            {
                return $null
            }
            return @{
                Resource = "KdsRootKey"
                Status = $rootKeyStatus
                TimeStamp = [datetime]::UtcNow
                Source = $ENV:COMPUTERNAME
                Detail = $testContext["LcAdTxt"].KdsRootKeyMissingRemediation
            }
        }
    }),
    #>

    (New-Object -Type ExternalADTest -Property @{
        TestName = "RequiredOrgUnitsExist"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

            $serverParams = @{}
            if ($testContext["AdServer"])
            {
                $serverParams += @{Server = $testContext["AdServer"]}
            }
            if ($testContext["AdCredentials"])
            {
                $serverParams += @{Credential = $testContext["AdCredentials"]}
            }

            $requiredOUs = @($testContext["ADOUPath"], $testContext["ComputersADOUPath"], $testContext["UsersADOUPath"])

            Log-Info -Message (" Checking for the existance of OUs: {0}" -f ($requiredOUs -join ", ")) -Type Info -Function "RequiredOrgUnitsExist"

            $results = $requiredOUs | ForEach-Object {
                $resultingOU = $null

                try {
                    $resultingOU = Get-ADOrganizationalUnit -Identity $_ -ErrorAction SilentlyContinue @serverParams
                }
                catch {
                }

                return @{
                    Resource    = $_
                    Status      = if ($resultingOU) { 'SUCCESS' } else { 'FAILURE' }
                    TimeStamp   = [datetime]::UtcNow
                    Source      = $ENV:COMPUTERNAME
                    Detail = ($testContext["LcAdTxt"].MissingOURemediation -f $_)
                }
            }

            return $results
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "PhysicalMachineObjectsExist"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

            $serverParams = @{}
            if ($testContext["AdServer"])
            {
                $serverParams += @{Server = $testContext["AdServer"]}
            }
            if ($testContext["AdCredentials"])
            {
                $serverParams += @{Credential = $testContext["AdCredentials"]}
            }

            $computerAdOuPath = $testContext["ComputersADOUPath"]
            $domainFQDN = $testContext["DomainFQDN"]

            $physicalHostsSetting = @($testContext["PhysicalMachineNames"] | Where-Object { -not [string]::IsNullOrEmpty($_) })

            Log-Info -Message (" Validating settings for physical hosts: {0}" -f ($physicalHostsSetting -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist"

            try {
                $allComputerObjects = Get-ADComputer -SearchBase $computerAdOuPath -Filter "*" @serverParams
            }
            catch {
                Log-Info -Message (" Failed to find any computer objects in ActiveDirectory. Inner exception: {0}" -f $_) -Type Error -Function "PhysicalMachineObjectsExist"
                $allComputerObjects = @()
            }

            $foundPhysicalHosts = @($allComputerObjects | Where-Object {$_.Name -in $physicalHostsSetting})
            $missingPhysicalHostEntries = @($physicalHostsSetting | Where-Object {$_ -notin $allComputerObjects.Name})

            Log-Info -Message (" Found {0} entries in AD : {1}" -f $foundPhysicalHosts.Count,($foundPhysicalHosts.Name -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist"

            $physicalHostsWithBadDnsName = $($foundPhysicalHosts | Where-Object { $_.DNSHostName -ne "$($_.Name).$domainFQDN" })
            $physicalHostsWithBadSAMAcct = $($foundPhysicalHosts | Where-Object { $_.SAMAccountName -ne "$($_.Name)$" })

            Log-Info -Message (" Found {0} entries with invalid DNS names: {1}" -f $physicalHostsWithBadDnsName.Count,(($physicalHostsWithBadDnsName | Select-Object -Property DNSHostName) -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist"
            Log-Info -Message (" Found {0} entries with invalid SAM account names: {1}" -f $physicalHostsWithBadSAMAcct.Count,(($physicalHostsWithBadSAMAcct | Select-Object -Property SAMAccountName) -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist"

            $results = @()

            $hasComputerEntries = ($foundPhysicalHosts -and $foundPhysicalHosts.Count -eq $physicalHostsSetting.Count)
            $allEntriesHaveCorrectDnsNames = (-not $physicalHostsWithBadDnsName -or $physicalHostsWithBadDnsName.Count -eq 0)
            $allEntriesHaveCorrectSamAcct = (-not $physicalHostsWithBadSAMAcct -or $physicalHostsWithBadSAMAcct.Count -eq 0)

            $results += @{
                Resource    = "PhysicalHostAdComputerEntries"
                Status      = if ($hasComputerEntries) { 'SUCCESS' } else { 'FAILURE' }
                TimeStamp   = [datetime]::UtcNow
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].HostsMissingRemediation -f ($missingPhysicalHostEntries -join ", "),$computerAdOuPath)
            }
            $results += @{
                Resource    = "PhysicalHostAdComputerDnsNames"
                Status      = if ($allEntriesHaveCorrectDnsNames) { 'SUCCESS' } else { 'FAILURE' }
                TimeStamp   = [datetime]::UtcNow
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].HostsWithIncorrectDnsNameRemediation -f ($physicalHostsWithBadDnsName.Name -join ", "))
            }
            $results += @{
                Resource    = "PhysicalHostAdComputerSamAccounts"
                Status      = if ($allEntriesHaveCorrectSamAcct) { 'SUCCESS' } else { 'FAILURE' }
                TimeStamp   = [datetime]::UtcNow
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].HostsWithIncorrectSamAcctRemediation -f ($physicalHostsWithBadDnsName.Name -join ", "))
            }

            return $results
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "ClusterExistsWithRequiredAcl"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

            $serverParams = @{}
            if ($TestContext["AdServer"])
            {
                $serverParams += @{Server = $TestContext["AdServer"]}
            }
            if ($TestContext["AdCredentials"])
            {
                $serverParams += @{Credential = $TestContext["AdCredentials"]}
            }

            $createChildAce = $null
            $readPropertyAce = $null
            $computersAdOuPath = $testContext["ComputersADOUPath"]
            $clusterName = $testContext["ClusterName"]

            Log-Info -Message (" Searching for '{0}' entry in OU '{1}'" -f $clusterName,$computersAdOuPath) -Type Info -Function "ClusterExistsWithRequiredAcl"

            try {
                $clusterComputerEntry = Get-ADComputer -SearchBase $computersAdOuPath -Filter "Name -eq '$clusterName'" @serverParams
            }
            catch {
                Log-Info -Message (" Failed to find '{0}' entry in OU '{1}'. Inner exception: {2}" -f $clusterName,$computersAdOuPath,$_) -Type Error -Function "ClusterExistsWithRequiredAcl"
                $clusterComputerEntry = $null
            }

            if ($clusterComputerEntry)
            {
                $clusterSID = $clusterComputerEntry.SID
                Log-Info -Message (" Found entry, SID: {0}" -f $clusterSID) -Type Info -Function "ClusterExistsWithRequiredAcl"

                # The AD module SHOULD install a drive that we can use to get ACLs. However, sometimes it isn't properly registered
                # especially if we just installed it. So verify that it's usable
                $adDriveName = "AD"
                $tempDriveName = "hciad"
                $adDriveObject = $null

                try
                {
                    $adProvider = Get-PSProvider -PSProvider ActiveDirectory
                    if ($adProvider -and $adProvider.Drives.Count -gt 0)
                    {
                        $adDriveObject = $adProvider.Drives | Where-Object {$_.Name -eq $adDriveName -or $_.Name -eq $tempDriveName}
                    }
                }
                catch {
                    Log-Info -Message (" Error while trying to access active directory PS drive. Will fall back to creating a new PS drive. Inner exception: {0}" -f $_) -Type Warning -Function "ClusterExistsWithRequiredAcl"
                }

                if (-not $adDriveObject)
                {
                    try {
                        # Add a new drive
                        $adDriveObject = New-PSDrive -Name $tempDriveName -PSProvider ActiveDirectory -Root '' @serverParams
                    }
                    catch {
                        Log-Info -Message (" Error while trying to create active directory PS drive. Inner exception: {0}" -f $_) -Type Error -Function "ClusterExistsWithRequiredAcl"
                    }
                }

                $accessRules = @()

                if ($adDriveObject)
                {
                    $adDriveName = $adDriveObject.Name

                    try
                    {
                        $ouPath = ("{0}:\{1}" -f $adDriveName,$computersAdOuPath)
                        $ouAcl = Get-Acl $ouPath
                    }
                    catch
                    {
                        Log-Info -Message (" Can't get acls from {0}. Inner exception: {1}" -f $ouPath,$_) -Type Error -Function "ClusterExistsWithRequiredAcl"
                    }
                    finally {
                        # best effort cleanup if we had added the temp drive
                        try
                        {
                            if ($adDriveName -eq $tempDriveName)
                            {
                                $adDriveObject | Remove-PSDrive
                            }
                        }
                        catch {}
                    }

                    try {
                        # must specify the type to retrieve -- need to get something comparable to the clusterSID
                        if ($ouAcl)
                        {
                            $accessRules = $ouAcl.GetAccessRules($true, $true, $clusterSID.GetType())
                        }
                    }
                    catch {
                        Log-Info -Message (" Error while trying to get access rules for OU. Inner exception: {0}" -f $_) -Type Error -Function "ClusterExistsWithRequiredAcl"
                    }
                }

                # Check that the CreateChild ACE has been added
                $createChildAce = $accessRules | Where-Object { `
                    $_.IdentityReference -eq $clusterSID -and `
                    $_.ActiveDirectoryRights -bor [System.DirectoryServices.ActiveDirectoryRights]::CreateChild -and `
                    $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and `
                    $_.ObjectType -eq ([System.Guid]::New('bf967a86-0de6-11d0-a285-00aa003049e2')) -and
                    $_.InheritanceType -eq [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
                }
                $readPropertyAce = $accessRules | Where-Object { `
                    $_.IdentityReference -eq $clusterSID -and `
                    $_.ActiveDirectoryRights -bor [System.DirectoryServices.ActiveDirectoryRights]::ReadProperty -and `
                    $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and `
                    $_.ObjectType -eq [System.Guid]::Empty -and
                    $_.InheritanceType -eq [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
                }

                Log-Info -Message (" Found CreateChild ACE: {0}" -f ([bool]$createChildAce)) -Type Info -Function "ClusterExistsWithRequiredAcl"
                Log-Info -Message (" Found ReadProperty ACE: {0}" -f ([bool]$readPropertyAce)) -Type Info -Function "ClusterExistsWithRequiredAcl"
            }

            return @{
                Resource    = "ClusterAcls"
                Status      = if ($createChildAce -and $readPropertyAce) { 'SUCCESS' } else { 'FAILURE' }
                TimeStamp   = [datetime]::UtcNow
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].ClusterAclsMissingRemediation -f $computersAdOuPath)
            }
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "LogSecurityGroups"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

            $serverParams = @{}
            if ($TestContext["AdServer"])
            {
                $serverParams += @{Server = $TestContext["AdServer"]}
            }
            if ($TestContext["AdCredentials"])
            {
                $serverParams += @{Credential = $TestContext["AdCredentials"]}
            }

            $usersOuPath = $testContext["UsersADOUPath"]

            try {
                $adGroups = Get-AdGroup -SearchBase $usersOuPath -Filter "GroupCategory -eq 'Security'" @serverParams
                Log-Info -Message ("Found {0} security groups in users OU:" -f $adGroups.Count) -Type Info -Function "LogSecurityGroups"
                Log-Info -Message (" {0}" -f (($adGroups | foreach-object {$_.Name}) -join ", ")) -Type Info -Function "LogSecurityGroups"
            }
            catch {
                Log-Info -Message ("Failed to get security groups in users OU. Inner exception: {0}" -f $_) -Type Warning -Function "LogSecurityGroups"
            }

            return @{
                Resource    = "SecurityGroups"
                Status      = 'SUCCESS'
                TimeStamp   = [datetime]::UtcNow
                Source      = $ENV:COMPUTERNAME
                Detail      = ""
            }
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "LogGmsaAccounts"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

            $usersOuPath = $testContext["UsersADOUPath"]

            $serverParams = @{}
            if ($TestContext["AdServer"])
            {
                $serverParams += @{Server = $TestContext["AdServer"]}
            }
            if ($TestContext["AdCredentials"])
            {
                $serverParams += @{Credential = $TestContext["AdCredentials"]}
            }

            try
            {
                $adGmsaAccounts = Get-ADServiceAccount -SearchBase $usersOuPath -Filter * @serverParams
                if ($adGmsaAccounts)
                {
                    Log-Info -Message ("Found {0} gMSA accounts in users OU:" -f $adGmsaAccounts.Count) -Type Info -Function "LogGmsaAccounts"
                    Log-Info -Message (" {0}" -f (($adGmsaAccounts | foreach-object {$_.Name}) -join ", ")) -Type Info -Function "LogGmsaAccounts"
                }
            }
            catch {
                Log-Info -Message ("Couldn't retrieve GMSA accounts. Inner exception: {0}" -f $_) -Type Warning -Function "GroupMembershipsExist"
            }

            #This check will be removed once we delete the dependency of Sto-SG security group.This will help us to catch if the customer provides a wrong deployment prefix.
            $securityGroups = @(
                "$($testContext["NamingPrefix"])-Sto-SG"
            )
            $usersOuPath = $testContext["UsersADOUPath"]
            $missingSecurityGroups = @()

            # Look up all the required security groups and identify any that are missing
            foreach ($securityGroup in $securityGroups)
            {
                try {
                    $adGroup = Get-AdGroup -SearchBase $usersOuPath -Filter { Name -eq $securityGroup } @serverParams
                }
                catch {
                    $adGroup = $null
                }

                if (-not $adGroup)
                {
                    $missingSecurityGroups += $securityGroup
                }
            }

            return @{
                Resource    = "GmsaAccounts"
                Status      = if ($missingSecurityGroups.Count -eq 0) { 'SUCCESS' } else { 'FAILURE' }
                TimeStamp   = [datetime]::UtcNow
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].SecurityGroupsMissingRemediation -f ($missingSecurityGroups -join ', '))
            }
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "GpoInheritanceIsBlocked"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

            $serverParams = @{}
            if ($TestContext["AdServer"])
            {
                $serverParams += @{Server = $TestContext["AdServer"]}
            }
            $ouList = @($testContext["ADOUPath"],$testContext["ComputersADOUPath"],$testContext["UsersADOUPath"])

            $ousWithoutGpoInheritanceBlocked = @()

            $accessWasDenied = $false

            try
            {
                foreach ($ouItem in $ouList)
                {
                    try
                    {
                        $gpInheritance = Get-GPInheritance -Target $ouItem @serverParams
                    }
                    catch
                    {
                        if ($_.Exception -is [System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException]) { throw }
                    }

                    if ((-not $gpInheritance) -or (-not $gpInheritance.GpoInheritanceBlocked))
                    {
                        $ousWithoutGpoInheritanceBlocked += $ouItem
                    }
                }
            }
            catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException]
            {
                $accessWasDenied = $true
            }

            $statusValue = 'SUCCESS'
            if ($ousWithoutGpoInheritanceBlocked.Count -ne 0)
            {
                $statusValue = 'FAILURE'
            }
            if ($accessWasDenied)
            {
                return $null
            }

            return @{
                Resource    = "OuGpoInheritance"
                Status      = $statusValue
                TimeStamp   = [datetime]::UtcNow
                Source      = $ENV:COMPUTERNAME
                Detail = $testContext["LcAdTxt"].OuInheritanceBlockedMissingRemediation
            }
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "ExecutingAsDeploymentUser"
        ExecutionBlock = {

            <#
            During deployment, the environment checker itself runs as a local admin account, but we need to make sure that the AD credentials that are passed in
            meet all the same criteria as if they were created as part of the AD pre creation tool script.
            As a result, the user specified with these credentials needs to have:
            * All access to the deployment OU in AD
            * Membership to a set of SGs as mentioned in the array below
            #>


            Param ([hashtable]$testContext)

            # Values retrieved from the test context
            $adOuPath = $testContext["ADOUPath"]
            $namingPrefix = $testContext["NamingPrefix"]
            $usersOuPath = $testContext["UsersADOUPath"]
            [pscredential]$credentials = $testContext["AdCredentials"]
            $credentialName = $null

            if ($credentials)
            {
                # Get the user SID so we can find it in the ACL
                $credentialParts = $credentials.UserName.Split("\\")
                $credentialName = $credentialParts[$credentialParts.Length-1]
            }
            else
            {
                $credentialName = $env:USERNAME
            }

            $serverParams = @{}
            if ($TestContext["AdServer"])
            {
                $serverParams += @{Server = $TestContext["AdServer"]}
            }
            if ($TestContext["AdCredentials"])
            {
                $serverParams += @{Credential = $TestContext["AdCredentials"]}
            }

            $userSID = $null
            try {
                $userSecurityIdentifier = Get-ADUser -Filter {Name -eq $credentialName} -SearchBase $adOuPath @serverParams
                if ($userSecurityIdentifier) {
                    $userSID = [System.Security.Principal.SecurityIdentifier] $userSecurityIdentifier.SID
                }
            }
            catch {
                Log-Info -Message (" Failed to get user '{0}' in Active Directory. Inner exception: {1}" -f $credentialName,$_) -Type Error -Function "ExecutingAsDeploymentUser"
            }

            if (-not $userSID)
            {
                return @{
                    Resource    = "ExecutingAsDeploymentUser"
                    Status      = "FAILURE"
                    TimeStamp   = [datetime]::UtcNow
                    Source      = $ENV:COMPUTERNAME
                    Detail = ($testContext["LcAdTxt"].ADUserNotFound -f $credentials.UserName,$adOuPath)
                }
            }
            else
            {
                Log-Info -Message (" Found user '{0}' in Active Directory" -f $credentialName) -Type Info -Function "ExecutingAsDeploymentUser"

                # Test whether the AdCredentials user has all access rights to the OU
                $userHasOuPermissions = $false
                try {

                    $adDriveName = "AD"
                    $tempDriveName = "hciad"
                    $adDriveObject = $null

                    try
                    {
                        $adProvider = Get-PSProvider -PSProvider ActiveDirectory
                        if ($adProvider -and $adProvider.Drives.Count -gt 0)
                        {
                            $adDriveObject = $adProvider.Drives | Where-Object {$_.Name -eq $adDriveName -or $_.Name -eq $tempDriveName}
                        }
                    }
                    catch {
                        Log-Info -Message (" Error while trying to access active directory PS drive. Will fall back to creating a new PS drive. Inner exception: {0}" -f $_) -Type Warning -Function "ExecutingAsDeploymentUser"
                    }

                    if (-not $adDriveObject)
                    {
                        try {
                            # Add a new drive
                            $adDriveObject = New-PSDrive -Name $tempDriveName -PSProvider ActiveDirectory -Root '' @serverParams
                        }
                        catch {
                            Log-Info -Message (" Error while trying to create active directory PS drive. Inner exception: {0}" -f $_) -Type Error -Function "ExecutingAsDeploymentUser"
                        }
                    }

                    $ouAcl = $null

                    if ($adDriveObject)
                    {
                        $adDriveName = $adDriveObject.Name

                        try
                        {
                            $ouPath = ("{0}:\{1}" -f $adDriveName,$adOuPath)
                            $ouAcl = Get-Acl $ouPath
                        }
                        catch
                        {
                            Log-Info -Message (" Can't get acls from {0}. Inner exception: {1}" -f $ouPath,$_) -Type Error -Function "ExecutingAsDeploymentUser"
                        }
                        finally {
                            # best effort cleanup if we had added the temp drive
                            try
                            {
                                if ($adDriveName -eq $tempDriveName)
                                {
                                    $adDriveObject | Remove-PSDrive
                                }
                            }
                            catch {}
                        }
                    }

                    if ($ouAcl) {
                        try {
                            # must specify the type to retrieve -- need to get something comparable to the userSID
                            $accessRules = $ouAcl.GetAccessRules($true, $true, $userSID.GetType())
                        }
                        catch {
                            Log-Info -Message (" Error while trying to get access rules for OU. Inner exception: {0}" -f $_) -Type Error -Function "ClusterExistsWithRequiredAcl"
                        }

                        # Check that the GenericAll ACE has been added
                        $genericAllAce = $accessRules | Where-Object { `
                            $_.IdentityReference -eq $userSID -and `
                            $_.ActiveDirectoryRights -bor [System.DirectoryServices.ActiveDirectoryRights]::GenericAll -and `
                            $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and `
                            $_.InheritanceType -eq [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
                        }

                        if ($genericAllAce)
                        {
                            $userHasOuPermissions = $true
                        }
                        else
                        {
                            Log-Info -Message (" Found ACLs for AD OU ({0}), but user ({1})'s SID ({2}) not granted GenericAll access" -f $ouPath,$credentialName,$userSID) -Type Warning -Function "ExecutingAsDeploymentUser"
                        }
                    }
                }
                catch {
                    Log-Info -Message (" FAILED to look up ACL for AD OU ({0}) and search for GenericAll ACE for user ({1}). Inner exception: {2}" -f $ouPath,$credentialName,$_) -Type Error -Function "ExecutingAsDeploymentUser"
                }

                $failureReasons = @()

                # Summarize detail based on what failed
                if (-not $userHasOuPermissions) {
                    $failureReasons += ($testContext["LcAdTxt"].CurrentUserMissingOUAccess -f $adOuPath)
                }

                if ($failureReasons.Count -gt 0) {
                    $statusValue = 'FAILURE'
                    $allFailureReasons = $failureReasons -join "; "
                    $detail = ($testContext["LcAdTxt"].CurrentUserFailureSummary -f $credentials.UserName,$allFailureReasons)
                }
                else
                {
                    $statusValue = 'SUCCESS'
                    $detail = ""
                }

                return @{
                    Resource    = "ExecutingAsDeploymentUser"
                    Status      = $statusValue
                    TimeStamp   = [datetime]::UtcNow
                    Source      = $ENV:COMPUTERNAME
                    Detail      = $detail
                }
            }
        }
    })
)

function Test-OrganizationalUnitOnSession {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $ADOUPath,

        [Parameter(Mandatory=$true)]
        [string]
        $DomainFQDN,

        [Parameter(Mandatory=$true)]
        [string]
        $NamingPrefix,

        [Parameter(Mandatory=$true)]
        [string]
        $ClusterName,

        [Parameter(Mandatory)]
        [array]
        $PhysicalMachineNames,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.Runspaces.PSSession]
        $Session,

        [Parameter(Mandatory=$false)]
        [string]
        $ActiveDirectoryServer,

        [Parameter(Mandatory=$false)]
        [pscredential]
        $ActiveDirectoryCredentials
    )

    $testContext = @{
        ADOUPath = $ADOUPath
        ComputersADOUPath = "OU=Computers,$ADOUPath"
        UsersADOUPath = "OU=Users,$ADOUPath"
        DomainFQDN = $DomainFQDN
        NamingPrefix = $NamingPrefix
        ClusterName = $ClusterName
        LcAdTxt = $lcAdTxt
        AdServer = $ActiveDirectoryServer
        AdCredentials = $ActiveDirectoryCredentials
        AdCredentialsUserName = if ($ActiveDirectoryCredentials) { $ActiveDirectoryCredentials.UserName } else { "" }
        PhysicalMachineNames = $PhysicalMachineNames
    }

    $computerName = if ($Session) { $Session.ComputerName } else { $ENV:COMPUTERNAME }

    Log-Info -Message "Executing test on $computerName" -Type Info

    # Reuse the parameters for Invoke-Command so that we only have to set up context and session data once
    $invokeParams = @{
        ScriptBlock = $null
        ArgumentList = $testContext
    }
    if ($Session) {
        $invokeParams += @{Session = $Session}
    }

    # If provided, verify the AD server and credentials are reachable
    if ($ActiveDirectoryServer -or $ActiveDirectoryCredentials)
    {
        $params = @{}
        if ($ActiveDirectoryServer)
        {
            $params["Server"] = $ActiveDirectoryServer
        }
        if ($ActiveDirectoryCredentials)
        {
            $params["Credential"] = $ActiveDirectoryCredentials
        }
        try {
            $null = Get-ADDomain @params
        }
        catch {
            if (-not $ActiveDirectoryServer) {
                $ActiveDirectoryServer = "default"
            }
            $userName = "default"
            if ($ActiveDirectoryCredentials) {
                $userName = $ActiveDirectoryCredentials.UserName
            }
            throw ("Unable to contact AD server {0} using {1} credentials. Internal exception: {2}" -f $ActiveDirectoryServer,$userName,$_)
        }
    }

    # Initialize the array of detailed results
    $detailedResults = @()

    # Test preparation -- fill in more of the test context that needs to be executed remotely
    $ExternalAdTestInitializors | ForEach-Object {
        $invokeParams.ScriptBlock = $_.ExecutionBlock
        $testName = $_.TestName

        Log-Info -Message "Executing test initializer $testName" -Type Info

        try
        {
            $results = Invoke-Command @invokeParams

            if ($results)
            {
                $testContext += $results
            }
        }
        catch {
            throw ("Unable to execute test {0} on {1}. Inner exception: {2}" -f $testName,$computerName,$_)
        }
    }

    Log-Info -Message "Executing tests with parameters: " -Type Info
    foreach ($key in $testContext.Keys)
    {
        if ($key -ne "LcAdTxt")
        {
            Log-Info -Message " $key : $($testContext[$key])" -Type Info
        }
    }

    # Update InvokeParams with the full context
    $invokeParams.ArgumentList = $testContext

    # For each test, call the test execution block and append the results
    $ExternalAdTests | ForEach-Object {
        # override ScriptBlock with the particular test execution block
        $invokeParams.ScriptBlock = $_.ExecutionBlock
        $testName = $_.TestName

        Log-Info -Message "Executing test $testName" -Type Info

        try
        {
            $results = Invoke-Command @invokeParams

            Log-Info -Message ("Test $testName completed with: {0}" -f $results) -Type Info

            $detailedResults += $results
        }
        catch {
            Log-Info -Message ("Test $testName FAILED. Inner exception: {0}" -f $_) -Type Info
        }
    }

    return $detailedResults
}

function Test-OrganizationalUnit {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $ADOUPath,

        [Parameter(Mandatory=$true)]
        [string]
        $DomainFQDN,

        [Parameter(Mandatory=$true)]
        [string]
        $NamingPrefix,

        [Parameter(Mandatory=$true)]
        [string]
        $ClusterName,

        [Parameter(Mandatory=$true)]
        [array]
        $PhysicalMachineNames,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession,

        [Parameter(Mandatory=$false)]
        [string]
        $ActiveDirectoryServer = $null,

        [Parameter(Mandatory=$false)]
        [pscredential]
        $ActiveDirectoryCredentials = $null
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    Log-Info -Message "Executing Test-OrganizationalUnit"
    $fullTestResults = Test-OrganizationalUnitOnSession -ADOUPath $ADOUPath -DomainFQDN $DomainFQDN -NamingPrefix $NamingPrefix -ClusterName $ClusterName -Session $PsSession -ActiveDirectoryServer $ActiveDirectoryServer -ActiveDirectoryCredentials $ActiveDirectoryCredentials -PhysicalMachineNames $PhysicalMachineNames

    # Build the results
    $TargetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
    $remediationValues = $fullTestResults | Where-Object -Property Status -NE 'SUCCESS' | Select-Object $Remediation
    $remediationValues = $remediationValues -join "`r`n"
    if (-not $remediationValues)
    {
        $remediationValues = ''
    }

    $testOuResult = @()
    foreach ($result in $fullTestResults)
    {
        $params = @{
            Name               = "AzStackHci_ExternalActiveDirectory_Test_OrganizationalUnit_$($result.Resource)"
            Title              = "Test AD Organizational Unit - $($result.Resource)"
            DisplayName        = "Test AD Organizational Unit - $($result.Resource)"
            Severity           = 'CRITICAL'
            Description        = 'Tests that the specified organizational unit exists and contains the proper sub-OUs'
            Tags               = @{}
            Remediation        = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-prerequisites'
            TargetResourceID   = "Test_AD_OU_$TargetComputerName"
            TargetResourceName = "Test_AD_OU_$TargetComputerName"
            TargetResourceType = 'ActiveDirectory'
            Timestamp          = [datetime]::UtcNow
            Status             = $result.Status
            AdditionalData     = $result
            HealthCheckSource  = $ENV:EnvChkrId
        }
        $testOuResult += New-AzStackHciResultObject @params
    }

    return $testOuResult
}
# SIG # Begin signature block
# MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCApTG5xiEJkPClO
# a3x66OXnmTQ4TNx3qJfm2a0T8knn8aCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGaIwghmeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEILxthPQjp1jEaF+UskmIAex1
# uUM5bIiYGrrcUQMLUes/MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAbhb2ALGjDYwiDpEbDFwOYn/Aye8ohX/36+Gb3+XHWp6+SW41M2B7wmCr
# Xr/isvyn99vQFpEe5ZoFVafDAkTP8tyX/pTdhejLaxrbNK8aepXv2V9M5yLSk0l0
# NbzNGtSb+6QiayUD4SSRa3ltOV8th8RH3b24LWrjqQ1ijzOW6IaeV/+WHJxs7jTG
# KRKGs7uzpgqPAdqwZmdcxVFofGhxqmYVU4jJSox2D9dbcAkMYySal3IsuTaZpOuF
# RQnjYpkuBkGqjFEpq7vG7o0vj6qrSgIum+RsBVq8or00ebn+KXgK3b6sZMKA3gmt
# fJTyaiesNgUAYqooX5U68NW8LoSaLaGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC
# FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAQ7/AnTSljVXjlSG9aH3239hEed1fdxrg63gqRffFhDAIGZbqeulZ0
# GBMyMDI0MDIxMjE0MDYyOC40NTZaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAHajtXJWgDREbEAAQAAAdowDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx
# MDEyMTkwNjU5WhcNMjUwMTEwMTkwNjU5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJOQBgh2tVFR1j8jQA4NDf8b
# cVrXSN080CNKPSQo7S57sCnPU0FKF47w2L6qHtwm4EnClF2cruXFp/l7PpMQg25E
# 7X8xDmvxr8BBE6iASAPCfrTebuvAsZWcJYhy7prgCuBf7OidXpgsW1y8p6Vs7sD2
# aup/0uveYxeXlKtsPjMCplHkk0ba+HgLho0J68Kdji3DM2K59wHy9xrtsYK+X9er
# bDGZ2mmX3765aS5Q7/ugDxMVgzyj80yJn6ULnknD9i4kUQxVhqV1dc/DF6UBeuzf
# ukkMed7trzUEZMRyla7qhvwUeQlgzCQhpZjz+zsQgpXlPczvGd0iqr7lACwfVGog
# 5plIzdExvt1TA8Jmef819aTKwH1IVEIwYLA6uvS8kRdA6RxvMcb//ulNjIuGceyy
# kMAXEynVrLG9VvK4rfrCsGL3j30Lmidug+owrcCjQagYmrGk1hBykXilo9YB8Qyy
# 5Q1KhGuH65V3zFy8a0kwbKBRs8VR4HtoPYw9z1DdcJfZBO2dhzX3yAMipCGm6Smv
# mvavRsXhy805jiApDyN+s0/b7os2z8iRWGJk6M9uuT2493gFV/9JLGg5YJJCJXI+
# yxkO/OXnZJsuGt0+zWLdHS4XIXBG17oPu5KsFfRTHREloR2dI6GwaaxIyDySHYOt
# vIydla7u4lfnfCjY/qKTAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUoXyNyVE9ZhOV
# izEUVwhNgL8PX0UwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBALmDVdTtuI0jAEt4
# 1O2OM8CU237TGMyhrGr7FzKCEFaXxtoqk/IObQriq1caHVh2vyuQ24nz3TdOBv7r
# cs/qnPjOxnXFLyZPeaWLsNuARVmUViyVYXjXYB5DwzaWZgScY8GKL7yGjyWrh78W
# JUgh7rE1+5VD5h0/6rs9dBRqAzI9fhZz7spsjt8vnx50WExbBSSH7rfabHendpeq
# bTmW/RfcaT+GFIsT+g2ej7wRKIq/QhnsoF8mpFNPHV1q/WK/rF/ChovkhJMDvlqt
# ETWi97GolOSKamZC9bYgcPKfz28ed25WJy10VtQ9P5+C/2dOfDaz1RmeOb27Kbeg
# ha0SfPcriTfORVvqPDSa3n9N7dhTY7+49I8evoad9hdZ8CfIOPftwt3xTX2RhMZJ
# CVoFlabHcvfb84raFM6cz5EYk+x1aVEiXtgK6R0xn1wjMXHf0AWlSjqRkzvSnRKz
# FsZwEl74VahlKVhI+Ci9RT9+6Gc0xWzJ7zQIUFE3Jiix5+7KL8ArHfBY9UFLz4sn
# boJ7Qip3IADbkU4ZL0iQ8j8Ixra7aSYfToUefmct3dM69ff4Eeh2Kh9NsKiiph58
# 9Ap/xS1jESlrfjL/g/ZboaS5d9a2fA598mubDvLD5x5PP37700vm/Y+PIhmp2fTv
# uS2sndeZBmyTqcUNHRNmCk+njV3nMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAQqIfIYljHUbNoY0/wjhXRn/sSA2ggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOl0RNYwIhgPMjAyNDAyMTIxNTE4NDZaGA8yMDI0MDIxMzE1MTg0NlowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA6XRE1gIBADAKAgEAAgIBRwIB/zAHAgEAAgISkjAK
# AgUA6XWWVgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEHMH8eDwQ5UEvYI
# N/9Kq6PkMHM1J4YpUps7G5vN9ACEiNOyRzo1u51vINRVtW35RkXd/FwSJxwy2FsB
# v/D0WmPV4Zm4NoWqDnB/Dpb4oBYpgmXmXTcFHbPKZldyI3/XMMJxnSLiJ6zmzbCt
# mhPIujUMoFKQpSrn7wOflyWjp5aaMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAHajtXJWgDREbEAAQAAAdowDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQg3U1WPFV2i2KCB1uXTS9GpUAo9EyM/ZGw/O7PZSsh4w8wgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCAipaNpYsDvnqTe95Dj1C09020I5ljibrW/ndIC
# Oxg9xjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# 2o7VyVoA0RGxAAEAAAHaMCIEIDnRr0iTxo7N39RYkJQjNKgSUzMlqo106/A0RY2l
# 63UJMA0GCSqGSIb3DQEBCwUABIICAE0uJkAiuF6zQtza3TvcOwWGhiojgf9YHjWW
# UxyvWJ3LpN+nSeLQveOj+3Let2LvAyHedBZ4ftsolXBVRxOYY9Jx+pS98ezhbexj
# 47TbmRbZnZVfZQfvV1tm0JnZUPhDP3XXKDQBDgu7W0WqEhgR1YmAyVok1sCwlE4q
# nDw0EhNvYx9xE74LGuNwPMsArWMDAwN7NYwpvQGl/Tg17Fl+Mfp8nMYUuYTMfsQa
# ewM7qWid3xrrv69CUq9stYs7TtjdnrPk+aQn8cKIDhaOFDZZEPuV5GK21vDMrFrM
# FKyEkdAd0zxd/WeOJPV5s2aVUWsV6J4jA6CZYWOYJ/4658TU/0uhCTwI2pZColtU
# YqxNeD0xasTRHgSXyQcEstyaSkLbRlczXkyeqAiaJ4vcyuxoJlcrEylEv63RhhUh
# m0pel0xGHuuTgPyYmvn8VEJlFP6lejflFoX4xNcrxHtrC/BCZ0ol35TjKfV0UpI1
# z64DacfL6zqHeCfoQX9i4aNLJaYGPVd00lAicdnw5zsEqtlOYpb0uvmruO9QaUGX
# SVnZgWxcMKUzdh7SzceBwvNqEuMYt25aW0UMpAAg8VRlZ4/8LOrsVI8tskoVGJly
# 5zEIyI3Rp0Wk733KgoF0spbwDJx2X9qReiQp/C+zcvUR7fBWbv6tjbvfBh75lw2r
# q9CoTysB
# SIG # End signature block