AzStackHciExternalActiveDirectory/AzStackHci.ExternalActiveDirectory.Tests.psm1

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

class HealthModel
{
    # Attributes for Azure Monitor schema
    [string]$Name #Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer.
    [string]$Title #User-facing name; one or more sentences indicating the direct issue.
    [string]$Severity #Severity of the result (Critical, Warning, Informational, Hidden) – this answers how important the result is. Critical is the only update-blocking severity.
    [string]$Description #Detailed overview of the issue and what impact the issue has on the stamp.
    [psobject]$Tags #Key-value pairs that allow grouping/filtering individual tests. For example, "Group": "ReadinessChecks", "UpdateType": "ClusterAware"
    [string]$Status #The status of the check running (i.e. Failed, Succeeded, In Progress) – this answers whether the check ran, and passed or failed.
    [string]$Remediation #Set of steps that can be taken to resolve the issue found.
    [string]$TargetResourceID #The unique identifier for the affected resource (such as a node or drive).
    [string]$TargetResourceName #The name of the affected resource.
    [string]$TargetResourceType #The type of resource being referred to (well-known set of nouns in infrastructure, aligning with Monitoring).
    [datetime]$Timestamp #The Time in which the HealthCheck was called.
    [psobject]$AdditionalData #Property bag of key value pairs for additional information.
    [string]$HealthCheckSource #The name of the services called for the HealthCheck (I.E. Test-AzureStack, Test-Cluster).
}

class OrganizationalUnitTestResult : HealthModel {}

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

$ExternalAdTestInitializors = @(
    (New-Object -Type ExternalADTest -Property @{
        TestName = "InitializePhysicalHosts"
        ExecutionBlock = {
            param (
                [Parameter()]
                [hashtable]$TestContext
            )

            $clusterName = $TestContext["ClusterName"]

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

            $stoSG =  "$($TestContext["NamingPrefix"])-Sto-SG"

            # This is a bit messed up, but I don't see another way to get this list
            $computerObjects = $null
            try
            {
                $groupObject = Get-ADGroup -Filter {Name -eq $stoSG} @serverParams
                if ($groupObject)
                {
                    $computerObjects = Get-ADGroupMember -Identity $groupObject.DistinguishedName @serverParams | Where-Object {$_.objectClass -eq 'computer'}
                }
            }
            catch {}

            # exclude:
            # * Anything that matches the cluster name (eg, s-cluster)
            # * SU1FileServer

            $physicalHosts = ($computerObjects | Where-Object { $_.Name -notin @("$clusterName", "SU1FileServer") }).DistinguishedName

            return @{PhysicalHosts = $physicalHosts}
        }
    })
)

$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) { 'Succeeded' } else { 'Failed' }
            if ($accessDenied)
            {
                $rootKeyStatus = 'Skipped'
            }
            return New-Object PSObject -Property @{
                Resource = "KdsRootKey"
                Status = $rootKeyStatus
                TimeStamp = Get-Date
                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"])

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

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

                return New-Object PSObject -Property @{
                    Resource    = $_
                    Status      = if ($resultingOU) { 'Succeeded' } else { 'Failed' }
                    TimeStamp   = Get-Date
                    Source      = $ENV:COMPUTERNAME
                    Detail = ($testContext["LcAdTxt"].MissingOURemediation -f $_)
                }
            }

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

            # TODO: I'm not totally sure this is required, need to confirm
            return $null
        }
    }),
    (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["PhysicalHosts"] | Where-Object { -not [string]::IsNullOrEmpty($_) })

            [array]$physicalHosts = $physicalHostsSetting | ForEach-Object { Get-ADComputer -Identity $_ @serverParams }

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

            $results = @()

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

            $results += New-Object PSObject -Property @{
                Resource    = "PhysicalHostAdComputerEntries"
                Status      = if ($hasComputerEntries) { 'Succeeded' } else { 'Failed' }
                TimeStamp   = Get-Date
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].HostsMissingRemediation -f $computerAdOuPath)
            }
            $results += New-Object PSObject -Property @{
                Resource    = "PhysicalHostAdComputerDnsNames"
                Status      = if ($hasComputerEntries -and $allEntriesHaveCorrectDnsNames) { 'Succeeded' } else { 'Failed' }
                TimeStamp   = Get-Date
                Source      = $ENV:COMPUTERNAME
                Detail = $testContext["LcAdTxt"].HostsWithIncorrectDnsNameRemediation
            }
            $results += New-Object PSObject -Property @{
                Resource    = "PhysicalHostAdComputerSamAccounts"
                Status      = if ($hasComputerEntries -and $allEntriesHaveCorrectSamAcct) { 'Succeeded' } else { 'Failed' }
                TimeStamp   = Get-Date
                Source      = $ENV:COMPUTERNAME
                Detail = $testContext["LcAdTxt"].HostsWithIncorrectSamAcctRemediation
            }

            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"]

            $clusterComputerEntry = Get-ADComputer -SearchBase $computersAdOuPath -Filter "Name -eq '$clusterName'" @serverParams

            if ($clusterComputerEntry)
            {
                $clusterSID = $clusterComputerEntry.SID

                # 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

                $adProvider = Get-PSProvider -PSProvider ActiveDirectory
                if ($adProvider.Drives.Count -gt 0)
                {
                    $adDriveObject = $adProvider.Drives | Where-Object {$_.Name -eq $adDriveName -or $_.Name -eq $tempDriveName}
                }

                if (-not $adDriveObject)
                {
                    # Add a new drive
                    $adDriveObject = New-PSDrive -Name $tempDriveName -PSProvider ActiveDirectory -Root '' @serverParams
                }

                $adDriveName = $adDriveObject.Name

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

                # must specify the type to retrieve -- need to get something comparable to the clusterSID
                $accessRules = $ouAcl.GetAccessRules($true, $true, $clusterSID.GetType())

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

            return New-Object PSObject -Property @{
                Resource    = "ClusterAcls"
                Status      = if ($createChildAce -and $readPropertyAce) { 'Succeeded' } else { 'Failed' }
                TimeStamp   = Get-Date
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].ClusterAclsMissingRemediation -f $computersAdOuPath)
            }
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "SecurityGroupsExist"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

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

            $securityGroups = @(
                "$($testContext["NamingPrefix"])-Sto-SG",
                "$($testContext["NamingPrefix"])-FsAcl-InfraSG",
                "$($testContext["NamingPrefix"])-FsAcl-AcsSG",
                "$($testContext["NamingPrefix"])-CertificateReadersSG",
                "$($testContext["NamingPrefix"])-Slb-VmSG",
                "$($testContext["NamingPrefix"])-Gw-VmSG",
                "$($testContext["NamingPrefix"])-FsAcl-SqlSG",
                "$($testContext["NamingPrefix"])-Fab-SrvSG",
                "$($testContext["NamingPrefix"])-HA-SrvSG",
                "$($testContext["NamingPrefix"])-EceSG",
                "$($testContext["NamingPrefix"])-BM-ECESG",
                "$($testContext["NamingPrefix"])-HA-R-SrvSG",
                "$($testContext["NamingPrefix"])-SB-Jea-LC-VmSG",
                "$($testContext["NamingPrefix"])-Hc-Rs-SrvSG",
                "$($testContext["NamingPrefix"])-Agw-SrvSG",
                "$($testContext["NamingPrefix"])-Hrp-HssSG",
                "$($testContext["NamingPrefix"])-IH-HsSG",
                "$($testContext["NamingPrefix"])-NC-VmSG",
                "$($testContext["NamingPrefix"])-Nc-BmSG",
                "$($testContext["NamingPrefix"])-OpsAdmin",
                "$($testContext["NamingPrefix"])-SB-Jea-MG-VmSG",
                "$($testContext["NamingPrefix"])-Nc-EceSG",
                "$($testContext["NamingPrefix"])-NC-FsSG",
                "$($testContext["NamingPrefix"])-Slb-EceSG",
                "$($testContext["NamingPrefix"])-FsAcl-PublicSG",
                "$($testContext["NamingPrefix"])-IH-MsSG"
            )

            $usersOuPath = $testContext["UsersADOUPath"]

            $missingSecurityGroups = @()

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

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

            $physicalHosts = $()

            try
            {
                # get the list of physical hosts
                foreach ($host in $testContext["PhysicalHosts"])
                {
                    $physicalHosts += ${Name=$host; Object=(Get-ADComputer -Identity $_ @serverParams); MissingSGs=@()}
                }

                # Now check that the physical machines have been added to the required SGs
                $physicalMachineSecurityGroups = @("$($testContext["NamingPrefix"])-Sto-SG")

                foreach ($physicalHost in $physicalHosts)
                {
                    foreach ($physicalMachineSecurityGroup in $physicalMachineSecurityGroups)
                    {
                        $isMember = $false
                        try
                        {
                            $groupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $physicalMachineSecurityGroup} @serverParams

                            if ($groupObject)
                            {
                                $isMember = Get-ADGroupMember -Identity $groupObject @serverParams | Where-Object {$_.SID -eq $($physicalHost.Object.SID)}
                            }
                        }
                        catch {}

                        if (-not $isMember)
                        {
                            $physicalHost.MissingSGs += $physicalMachineSecurityGroup
                        }
                    }
                }
            }
            catch
            {

            }

            # Do we need to check for the deployment user

            $results = @()

            $results += New-Object PSObject -Property @{
                Resource    = "SecurityGroups"
                Status      = if ($missingSecurityGroups.Count -eq 0) { 'Succeeded' } else { 'Failed' }
                TimeStamp   = Get-Date
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].SecurityGroupsMissingRemediation -f ($missingSecurityGroups -join ', '))
            }

            foreach ($physicalHost in $physicalHosts)
            {
                $missingSecurityGroupMemberships = $physicalHost.MissingSGs

                $results += New-Object PSObject -Property @{
                    Resource    = "SecurityGroupMembership_$($physicalHost.Name)"
                    Status      = if ($missingSecurityGroupMemberships.Count -eq 0) { 'Succeeded' } else { 'Failed' }
                    TimeStamp   = Get-Date
                    Source      = $ENV:COMPUTERNAME
                    Detail = ($testContext["LcAdTxt"].HostSecurityGroupsMissingRemediation -f $physcialHost.Name,($missingSecurityGroupMemberships -join ', '))
                }
            }

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

            $usersOuPath = $testContext["UsersADOUPath"]

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

            $gmsaAccounts = @(
                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-ECE";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/ae3299a9-3e87-4186-bd99-c43c9ae6a571");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-SqlSG", "$($testContext["NamingPrefix"])-Fab-SrvSG", "$($testContext["NamingPrefix"])-HA-SrvSG", "$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-BM-ECESG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-ALM";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-NC-ALM";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-LB-ALM";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Slb-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-GW-ALM";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Gw-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-FCA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-NC-FCA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-LB-FCA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Slb-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-GW-FCA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Gw-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-FRA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-NC-FRA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-TCA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-NC-TCA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-Nc-HA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/NC/1b4dde6b-7ea8-407a-8c9e-f86e8b97fd1c");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-HA-R-SrvSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-HA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/PhysicalNode/1b4dde6b-7ea8-407a-8c9e-f86e8b97fd1c");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-HA-R-SrvSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SB-LC";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG", "$($testContext["NamingPrefix"])-SB-Jea-LC-VmSG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/754dbc04-8f91-4cb6-a10f-899dac573fa0");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-SB-Jea-LC-VmSG", "$($testContext["NamingPrefix"])-Sto-SG" ) },

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SB-Jea";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-Sto-SG" ) },

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SB-MG";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG", "$($testContext["NamingPrefix"])-SB-Jea-MG-VmSG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/ea126685-c89e-4294-959f-bba6bf75b4aa");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-SB-Jea-MG-VmSG", "$($testContext["NamingPrefix"])-Sto-SG" ) },

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SBJeaM";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-Sto-SG" ) },

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-NC-SA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-Nc-VmSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-Nc-TS";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@();
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-EceSA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/4dde37cc-6ee0-4d75-9444-7061e156507f");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-FsAcl-sqlSG", "$($testContext["NamingPrefix"])-Fab-SrvSG", "$($testContext["NamingPrefix"])-HA-SrvSG", "$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-BM-EceSG",  "$($testContext["NamingPrefix"])-Nc-EceSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-NC-ECE";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Nc-VmSG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/128fd370-07e4-41ac-876f-738c3c4cfd1b");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-sqlSG", "$($testContext["NamingPrefix"])-Fab-SrvSG", "$($testContext["NamingPrefix"])-HA-SrvSG","$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-Nc-EceSG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG")},


                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-Urp-SA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/110bac92-1879-47ae-9611-e40f8abf4fc0");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-PublicSG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-BM-ECESG", "$($testContext["NamingPrefix"])-Fab-SrvSG",  "$($testContext["NamingPrefix"])-EceSG")},

                [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-MSA";
                                     PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG");
                                     ServicePrincipalName=@("$($testContext["NamingPrefix"])/PhysicalNode/d8c180f6-7290-458e-90f0-96894f45e981");
                                     MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG",  "$($testContext["NamingPrefix"])-IH-MsSG", "$($testContext["NamingPrefix"])-HA-R-SrvSG")}
            )

            $missingGmsaAccounts = @()

            foreach ($gmsaAccount in $gmsaAccounts)
            {
                $accountMissing = $true
                try
                {
                    $gmsaName = $gmsaAccount.GmsaName
                    $adGmsaAccount = Get-ADServiceAccount -SearchBase $usersOuPath -Filter {Name -eq $gmsaName} @serverParams
                    if ($adGmsaAccount)
                    {
                        # TODO, identify SPNs and make sure they match

                        # TODO, identify PrincipasAllowedToRetrieveManagedPassword and check

                        $isMember = $true
                        try
                        {
                            foreach ($memberOfGroup in $gmsaAccount.MemberOf)
                            {
                                $groupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $gmsaAccount} @serverParams

                                if ($groupObject)
                                {
                                    $isMemberOfThisGroup = Get-ADGroupMember -Identity $groupObject @serverParams | Where-Object {$_.SID -eq $($adGmsaAccount.SID)}

                                    if (-not $isMemberOfThisGroup)
                                    {
                                        $isMember = $false
                                    }
                                }
                            }
                        }
                        catch {}

                        $accountMissing = -not $isMember
                    }
                }
                catch {}

                if ($accountMissing)
                {
                    $missingGmsaAccounts += $gmsaAccount.GmsaName
                }
            }

            return New-Object PSObject -Property @{
                Resource    = "GmsaAccounts"
                Status      = if ($missingGmsaAccounts.Count -eq 0) { 'Succeeded' } else { 'Failed' }
                TimeStamp   = Get-Date
                Source      = $ENV:COMPUTERNAME
                Detail = ($testContext["LcAdTxt"].GmsaAccountsMissingRemediation -f ($missingGmsaAccounts -join ', '))
            }
        }
    }),
    (New-Object -Type ExternalADTest -Property @{
        TestName = "GroupMembershipsExist"
        ExecutionBlock = {
            Param ([hashtable]$testContext)

            $usersOuPath = $testContext["UsersADOUPath"]

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

            $SecurityGroupMemberships = @(
                [pscustomobject]@{ Name="$($testContext["NamingPrefix"])-HA-R-SrvSG";
                                   MemberOf=@("$($testContext["NamingPrefix"])-Hc-Rs-SrvSG", "$($testContext["NamingPrefix"])-Agw-SrvSG", "$($testContext["NamingPrefix"])-Hrp-HssSG", "$($testContext["NamingPrefix"])-IH-HsSG",  "$($testContext["NamingPrefix"])-IH-MsSG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG");
                                   MissingMemberships=@()},

                [pscustomobject]@{ Name="$($testContext["NamingPrefix"])-NC-VmSG";
                                   MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-AcsSG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG");
                                   MissingMemberships=@()}
            )

            $results = @()

            foreach ($securityGroupMembership in $SecurityGroupMemberships)
            {
                $sgName = $securityGroupMembership.Name
                $sgMemberList = $securityGroupMembership.MemberOf
                $groupObject = $null
                try
                {
                    $groupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $sgName} @serverParams
                }
                catch {}

                if ($groupObject)
                {
                    foreach ($securityGroupName in $sgMemberList)
                    {
                        $isMember = $false
                        try
                        {
                            $parentGroupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $securityGroupName} @serverParams

                            if ($groupObject)
                            {
                                $isMember = Get-ADGroupMember -Identity $parentGroupObject @serverParams | Where-Object {$_.SID -eq $($groupObject.SID)}
                            }
                        }
                        catch {}

                        if (-not $isMember)
                        {
                            $securityGroupMembership.MissingMemberships += $securityGroupName
                        }
                    }
                }
                else {
                    $securityGroupMembership.MissingMemberships = $sgMemberList
                }

                $results +=  New-Object PSObject -Property @{
                    Resource    = "NestedSecurityGroups_$sgName"
                    Status      = if ($securityGroupMembership.MissingMemberships.Count -eq 0) { 'Succeeded' } else { 'Failed' }
                    TimeStamp   = Get-Date
                    Source      = $ENV:COMPUTERNAME
                    Detail = ($testContext["LcAdTxt"].NestedSecurityGroupsMissingRemediation -f $sgName,($securityGroupMembership.MissingMemberships -join ', '))
                }
            }

            return $results
        }
    }),
    (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 = 'Succeeded'
            if ($ousWithoutGpoInheritanceBlocked.Count -ne 0)
            {
                $statusValue = 'Failed'
            }
            if ($accessWasDenied)
            {
                $statusValue = 'Skipped'
            }


            return New-Object PSObject -Property @{
                Resource    = "OuGpoInheritance"
                Status      = $statusValue
                TimeStamp   = Get-Date
                Source      = $ENV:COMPUTERNAME
                Detail = $testContext["LcAdTxt"].OuInheritanceBlockedMissingRemediation
            }
        }
    })
)

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=$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

    }

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

    # 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
            throw ("Unable to execute test {0} on {1}. Inner exception: {2}" -f $testName,$computerName,$_)
        }
    }

    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=$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

    # Build the results
    $now = Get-Date
    $TargetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
    $aggregateStatus = if ($fullTestResults.Status -notcontains 'Failed') { 'Succeeded' } else { 'Failed' }
    $remediationValues = $fullTestResults | Where-Object -Property Status -NE 'Succeeded' | Select-Object $Remediation
    $remediationValues = $remediationValues -join "`r`n"
    if (-not $remediationValues)
    {
        $remediationValues = ''
    }
    $testOuResult = New-Object -Type OrganizationalUnitTestResult -Property @{
        Name               = 'AzStackHci_ExternalActiveDirectory_Test_OrganizationalUnit'
        Title              = 'Test AD Organizational Unit'
        Severity           = 'Critical'
        Description        = 'Tests that the specified organizational unit exists and contains the proper sub-OUs'
        Tags               = $null
        Remediation        = 'https://aka.ms/hci-connect-chk'
        TargetResourceID   = "Test_AD_OU_$TargetComputerName"
        TargetResourceName = "Test_AD_OU_$TargetComputerName"
        TargetResourceType = 'ActiveDirectory'
        Timestamp          = $now
        Status             = $aggregateStatus
        AdditionalData     = $fullTestResults
        HealthCheckSource  = ((Get-PSCallStack)[-1].Command)
    }
    return $testOuResult
}
# SIG # Begin signature block
# MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAIiQEup/BMuR/i
# rIijmiG0mskcaTTfoKAatJMRUWtI46CCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMbsg9RLt/owb3FAk1JjD6DY
# 85Y07RCcJM5hZ2Pe3jIWMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAAZc3Y/4Q+oBFRO5hkH/t3PD/gc/I8XV4a3iF3RPr4HscZadK1r5mufth
# Z+K78lCYTw3mfGu+NvnBen3kmjymiKWloJW1PeJ7Gm1I3Mf9mrntUZX8FK2y+tOO
# n5u4HBESdXmHzeqDGw+a8tWBU+BXQ/m5NBmGFMt/7zXOZYfoGmuVEK69Amv9MY05
# aAboL/tcWR70En8G+Sh15zSphqq1OKPPkfBbE9ZWI1Ye4HSgoqfW6lwZYK6/994l
# UUJRr5uVWIFAbLk+YS/xD2elXiJN3BddG51PHEVZSeTgF4HpvEGP6BkPAZsNYY83
# Z9rvaqWlE01MKFeKgitLyKsCqmc9S6GCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCgtbT+uhWJT1G0448nM2/PR5NwE2B/aQJ1sq4goHJh5gIGY3TU9JyO
# GBMyMDIyMTIwNzA3MTE1OS4zMjRaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAGybkADf26plJIAAQAAAbIwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjAxWhcNMjMxMjE0MjAyMjAxWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqiZTIde/lQ4rC+Bml5f/Wu
# q/xKTxrfbG23HofmQ+qZAN4GyO73PF3y9OAfpt7Qf2jcldWOGUB+HzBuwllYyP3f
# x4MY8zvuAuB37FvoytnNC2DKnVrVlHOVcGUL9CnmhDNMA2/nskjIf2IoiG9J0qLY
# r8duvHdQJ9Li2Pq9guySb9mvUL60ogslCO9gkh6FiEDwMrwUr8Wja6jFpUTny8tg
# 0N0cnCN2w4fKkp5qZcbUYFYicLSb/6A7pHCtX6xnjqwhmJoib3vkKJyVxbuFLRhV
# XxH95b0LHeNhifn3jvo2j+/4QV10jEpXVW+iC9BsTtR69xvTjU51ZgP7BR4YDEWq
# 7JsylSOv5B5THTDXRf184URzFhTyb8OZQKY7mqMh7c8J8w1sEM4XDUF2UZNy829N
# VCzG2tfdEXZaHxF8RmxpQYBxyhZwY1rotuIS+gfN2eq+hkAT3ipGn8/KmDwDtzAb
# nfuXjApgeZqwgcYJ8pDJ+y/xU6ouzJz1Bve5TTihkiA7wQsQe6R60Zk9dPdNzw0M
# K5niRzuQZAt4GI96FhjhlUWcUZOCkv/JXM/OGu/rgSplYwdmPLzzfDtXyuy/GCU5
# I4l08g6iifXypMgoYkkceOAAz4vx1x0BOnZWfI3fSwqNUvoN7ncTT+MB4Vpvf1QB
# ppjBAQUuvui6eCG0MCVNAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUmfIngFzZEZlP
# kjDOVluBSDDaanEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBANxHtu3FzIabaDbW
# qswdKBlAhKXRCN+5CSMiv2TYa4i2QuWIm+99piwAhDhADfbqor1zyLi95Y6GQnvI
# WUgdeC7oL1ZtZye92zYK+EIfwYZmhS+CH4infAzUvscHZF3wlrJUfPUIDGVP0lCY
# Vse9mguvG0dqkY4ayQPEHOvJubgZZaOdg/N8dInd6fGeOc+0DoGzB+LieObJ2Q0A
# tEt3XN3iX8Cp6+dZTX8xwE/LvhRwPpb/+nKshO7TVuvenwdTwqB/LT6CNPaElwFe
# KxKrqRTPMbHeg+i+KnBLfwmhEXsMg2s1QX7JIxfvT96md0eiMjiMEO22LbOzmLMN
# d3LINowAnRBAJtX+3/e390B9sMGMHp+a1V+hgs62AopBl0p/00li30DN5wEQ5If3
# 5Zk7b/T6pEx6rJUDYCti7zCbikjKTanBnOc99zGMlej5X+fC/k5ExUCrOs3/VzGR
# CZt5LvVQSdWqq/QMzTEmim4sbzASK9imEkjNtZZyvC1CsUcD1voFktld4mKMjE+u
# DEV3IddD+DrRk94nVzNPSuZXewfVOnXHSeqG7xM3V7fl2aL4v1OhL2+JwO1Tx3B0
# irO1O9qbNdJk355bntd1RSVKgM22KFBHnoL7Js7pRhBiaKmVTQGoOb+j1Qa7q+ci
# xGo48Vh9k35BDsJS/DLoXFSPDl4mMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# 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
# tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAjhJ+EeySRfn2KCNsjn9cF9AUSTqggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOc6WbEwIhgPMjAyMjEyMDcwODE1NDVaGA8yMDIyMTIwODA4MTU0NVowdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA5zpZsQIBADAHAgEAAgIVvzAHAgEAAgISWTAKAgUA
# 5zurMQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJp6zKQ8puAborH35AEc
# wG5Qfv3t6qk8i/T+VR/krx6or9mqntRliShdEQIYuOJg8wk/efedGd3NgU3q29nF
# +NYH1cGRSZK1jpOnGm8uJ7/1qlWRVt6MrDfi0nI6rlUJhAQcr78tu0+VHxhxRMed
# ijqzQ0IHZhZ7zKIR3ZfTBeccMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAGybkADf26plJIAAQAAAbIwDQYJYIZIAWUDBAIB
# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx
# IgQgJsKqnstY58ynrmV8MVFD7h4qMIEdXH5EfOX3M3O4e9kwgfoGCyqGSIb3DQEJ
# EAIvMYHqMIHnMIHkMIG9BCBTeM485+E+t4PEVieUoFKX7PVyLo/nzu+htJPCG04+
# NTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABsm5A
# A39uqZSSAAEAAAGyMCIEIIKVSifTyy/rHSq1vKNFeWJUf+Ve4Sw/rwVzzLsofNh9
# MA0GCSqGSIb3DQEBCwUABIICABgHszfLJ9MlsvTO4Ip3QjxWMXxrQQq+gSCD5FVs
# JmJyT5pGXMxXcYjfRleCHJXmpjJtCnBEJRTqEAGaFPLMRcyDyNA85Am85MJVLn0B
# +lvSEwdP+aHvHRse33+2qgQzu/H/OuItvbN650WlQpLOIvaN7LG/Tr2FMf5rQTnX
# 63914X0DbOgICk20BmWF+VLRgm4hHW/moZeHkBt5sTGNA4GLZ6p2L3S0J5Uq28+n
# hTJurtD5a+vWVhkPTUoifvqhBI8uKIVcVWNLsa1UrfFchZ1emehQn/x5Ytto292n
# 84cPUmeXlVHqg4xgZXQ/ItSC+2VZyrCkcEYkqYfE662nST+MxTa/lcdib2edl8IZ
# EV+kVnc4HtxKIr8UP/RYMA6t50yxVjOzveYpOu+hxdlmZjC8LAHpCplPpdiZWLcc
# oM3CZRQnmzIp/19a+vE+9PzWIFchJEakxAN7OZNVIkI/G6KsZ8vX7f1MYjwpTNW2
# uUmijcpWD4H9iT2kcifKVXhZuTUaqkZKcGIcAz4yE0eYGV1b8hdxwOEnAGUGxvkc
# tAYPh1dpHNn4roAKFnFOW9hz3ewe+QNBk2So2pM/osrtIaFLKjL+6uSF88vToLv7
# IWG0lSI+PeXvYQFUCTObyrFiENg9PX0Azz3/dAD1vwMYWwzwHLsSjvNT+vW6PKbT
# 4Wy1
# SIG # End signature block