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 |