AzStackHciExternalActiveDirectory/AzStackHci.ExternalActiveDirectory.Tests.psm1
|
Import-LocalizedData -BindingVariable lcAdTxt -FileName AzStackHci.ExternalActiveDirectory.Strings.psd1 class ExternalADTest { [string]$TestName [scriptblock]$ExecutionBlock } $ExternalAdTestInitializors = @( ) $ExternalAdTests = @( (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"]} } $requiredOU = $testContext["ADOUPath"] Log-Info -Message (" Checking for the existance of OU: {0}" -f $requiredOU) -Type Info -Function "RequiredOrgUnitsExist" try { $resultingOU = Get-ADOrganizationalUnit -Identity $requiredOU -ErrorAction SilentlyContinue @serverParams } catch { } return @{ Resource = $_ Status = if ($resultingOU) { 'SUCCESS' } else { 'FAILURE' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].MissingOURemediation -f $_) } } }), (New-Object -Type ExternalADTest -Property @{ TestName = "LogPhysicalMachineObjectsIfExist" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($testContext["AdServer"]) { $serverParams += @{Server = $testContext["AdServer"]} } if ($testContext["AdCredentials"]) { $serverParams += @{Credential = $testContext["AdCredentials"]} } $adOUPath = $testContext["ADOUPath"] $domainFQDN = $testContext["DomainFQDN"] $seedNode = $ENV:COMPUTERNAME $detailedErrors = @() #Todo:: Check the domain status for all the nodes, till then don't fail this test case and just log the details for infomration purpose. Log-Info -Message (" Validating seednode : {0} is part of a domain or not " -f $seedNode) -Type Info -Function "PhysicalMachineObjectsExist" $isDomainJoined = (gwmi win32_computersystem).partofdomain $operationType = $ENV:EnvChkrId Log-Info -Message (" Env checker id :: {0}" -f $operationType) -Type Info -Function "PhysicalMachineObjectsExist" #Execute the below test if and only if when the machine is not domain joined and operationType does not contain "keepstorage" if ((-not $isDomainJoined) -and ($operationType -notmatch "keepstorage")) { $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 $adOUPath -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}) if ($foundPhysicalHosts.count -gt 0) { foreach ($physicalHost in $foundPhysicalHosts) { $detailedErrors += " Computer object for {0} found in AD and it's DistinguishedName :: {1}. Please remove the computer object." -f $($physicalHost.Name), $($physicalHost.DistinguishedName) } } $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" $timeoutSeconds = 60 if ($missingPhysicalHostEntries.Count -gt 0) { $jobs = foreach ($physicalHost in $missingPhysicalHostEntries) { start-job -ScriptBlock { param($computerName, $serverparams) Import-Module ActiveDirectory Get-ADComputer -Filter "Name -eq '$computerName' " @serverparams } -ArgumentList $physicalHost, $serverParams } # Wait for all jobs to complete or timeout Wait-Job -Job $jobs -Timeout $timeoutSeconds | Out-Null # Check the status of each job and display the results foreach ($job in $jobs) { $status = $job.State if ($status -eq 'Completed') { $result = Receive-Job -Job $job -ErrorAction SilentlyContinue if ($result) { Log-Info -Message (" Found physical host {0} in AD and it's DistinguishedName :: {1}. Please remove the computer object." -f $($result.Name), $($result.DistinguishedName)) -Type Error -Function "PhysicalMachineObjectsExist" $detailedErrors += " Found physical host {0} in AD and it's DistinguishedName :: {1}. Please remove the computer object." -f $($result.Name), $($result.DistinguishedName) } } elseif ($status -eq 'Running') { Stop-Job -Job $job -ErrorAction Ignore | Out-Null } # Clean up the job Remove-Job -Job $job -ErrorAction Ignore | Out-Null } } } # We are not failing this test case and getting the information for logging purpose. if ($detailedErrors.Count -gt 0) { $detail = $detailedErrors -join "; " $statusValue = 'SUCCESS' } else { $statusValue = 'SUCCESS' $detail = "" } $results += @{ Resource = "PhysicalHostAdComputerEntries" Status = $statusValue TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $detail } return $results } }), (New-Object -Type ExternalADTest -Property @{ TestName = "LogClusterObjectIfExist" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($testContext["AdServer"]) { $serverParams += @{Server = $testContext["AdServer"]} } if ($testContext["AdCredentials"]) { $serverParams += @{Credential = $testContext["AdCredentials"]} } $adOUPath = $testContext["ADOUPath"] $domainFQDN = $testContext["DomainFQDN"] $seedNode = $ENV:COMPUTERNAME $clusterName = $testContext["ClusterName"] $detail = "" Log-Info -Message (" Validating cluster object {0} is available in {1} " -f $clusterName, $adOUPath) -Type Info -Function "LogClusterObjectIfExist" $statusValue = 'SUCCESS' try { $clusterObject = Get-ADComputer -SearchBase $adOUPath -Filter "Name -eq '$clusterName' " @serverParams } catch { Log-Info -Message (" Failed to find cluster objects in ActiveDirectory. Inner exception: {0}" -f $_) -Type Error -Function "LogClusterObjectIfExist" $detail = " Failed to find cluster objects in ActiveDirectory. Inner exception: {0}" -f $_ $statusValue = 'FAILURE' } if ($clusterObject) { $detail = ("Cluster object {0} available in {1}" -f $clusterName, $adOUPath) } else { $timeoutSeconds = 60 Log-Info -Message (" Cluster object {0} not found in {1} " -f $clusterName, $adOUPath) -Type Info -Function "LogClusterObjectIfExist" $job = start-job -ScriptBlock { param($clusterName, $serverparams) Import-Module ActiveDirectory Get-ADComputer -Filter "Name -eq '$clusterName' " @serverparams } -ArgumentList $clusterName, $serverParams Wait-Job -Job $job -Timeout $timeoutSeconds | Out-Null $status = $job.State if ($status -eq 'Completed') { $result = Receive-Job -Job $job -ErrorAction SilentlyContinue if ($result) { Log-Info -Message (" Found cluster {0} in AD and it's DistinguishedName :: {1}. Please remove the computer object." -f $($result.Name), $($result.DistinguishedName)) -Type Error -Function "LogClusterObjectIfExist" $detail = " Found cluster {0} in AD and it's DistinguishedName :: {1}. Please remove the computer object." -f $($result.Name), $($result.DistinguishedName) $statusValue = 'FAILURE' } else { $detail = (" cluster object {0} not found in active directory." -f $clusterName) } } elseif ($status -eq 'Running') { $detail = " Unable to get the cluster object within the timeout and assuming that cluster object is not available on AD" Stop-Job -Job $job -ErrorAction Ignore | Out-Null } else { $detail = " Unable to get the cluster object and assuming that cluster object is not available on AD" } Remove-Job -Job $job -ErrorAction Ignore | Out-Null } $results += @{ Resource = "ClusterObject" Status = $statusValue TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $detail } return $results } }), (New-Object -Type ExternalADTest -Property @{ TestName = "GpoInheritanceIsBlocked" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($testContext["AdServer"]) { $serverParams += @{Server = $testContext["AdServer"]} } if ($testContext["AdCredentials"]) { $serverParams += @{Credential = $testContext["AdCredentials"]} } $ouPath = $testContext["ADOUPath"] Log-Info -Message (" Checking whether gpInheritance is blocked for the OU : {0} " -f $ouPath) -Type Info -Function "GpoInheritanceIsBlocked" $statusValue = 'FAILURE' try { $ou = Get-ADOrganizationalUnit -Identity $ouPath -Properties gpOptions @serverParams if ($ou.gpOptions -ne $null) { if ($ou.gpOptions -eq 1) { $statusValue = 'SUCCESS' Log-Info -Message (" gpInheritance is blocked for the OU : {0} and the gpOptions value : {1} " -f $ouPath, $($ou.gpOptions)) -Type Info -Function "GpoInheritanceIsBlocked" } else { Log-Info -Message (" gpInheritance is not blocked for the OU : {0} and the gpOptions value : {1} " -f $ouPath, $($ou.gpOptions)) -Type Info -Function "GpoInheritanceIsBlocked" } } else { Log-Info -Message (" gpInheritance is not blocked for the OU : {0} and unable to get the gpOptions property." -f $ouPath) -Type Info -Function "GpoInheritanceIsBlocked" } } catch { Log-Info -Message (" Failed to get the gpInheritance for the OU : {0} and Inner exception: {1}" -f $ouPath, $_) -Type Info -Function "GpoInheritanceIsBlocked" } return @{ Resource = "OuGpoInheritance" Status = $statusValue TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $testContext["LcAdTxt"].OuInheritanceBlockedMissingRemediation } } }), (New-Object -Type ExternalADTest -Property @{ TestName = "ValidateComputerAccountsInAD" ExecutionBlock = { Param ([hashtable]$testContext) $adComputerObjectTests = Test-ADComputerObjects $testContext return @{ Resource = "ValidateComputerAccountsInAD" Status = if ($adComputerObjectTests["Result"]) { 'SUCCESS' } else { 'FAILURE' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $adComputerObjectTests["FailureReasons"] } } }), (New-Object -Type ExternalADTest -Property @{ TestName = "ConnectivityTests" ExecutionBlock = { Param ([hashtable]$testContext) $connectivityTests = Test-Connectivity $testContext return @{ Resource = "ConnectivityTest" Status = if ($connectivityTests["Result"]) { 'SUCCESS' } else { 'FAILURE' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $connectivityTests["FailureReasons"] } } }), (New-Object -Type ExternalADTest -Property @{ TestName = "ExecutingAsDeploymentUser" ExecutionBlock = { Param ([hashtable]$testContext) # Values retrieved from the test context $adOuPath = $testContext["ADOUPath"] [pscredential]$credentials = $testContext["AdCredentials"] $credentialName = $null $statusValue = 'FAILURE' $userHasOuPermissions = $false 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"]} } $timeoutSeconds = 300 $failureReasons = @() try { $deploymentUserIdentifier = Get-ADUser -Identity $credentialName -SearchBase $adOuPath @serverParams } catch { Log-Info -Message (" LCM user '{0}' not found in {1} " -f $credentialName, $adOuPath) -Type Info -Function "ExecutingAsDeploymentUser" } # If the LCM user is not part of the OUPath try to query the entire AD and get the details" if (-not $deploymentUserIdentifier) { $lcmUserJob = start-job -ScriptBlock { param($lcmUserName, $serverparams) Import-Module ActiveDirectory Get-ADUser -Identity $lcmUserName @serverParams } -ArgumentList $credentialName, $serverParams Wait-Job -Job $lcmUserJob -Timeout $timeoutSeconds | Out-Null $status = $lcmUserJob.State if ($status -eq 'Completed') { $deploymentUserIdentifier = Receive-Job -Job $lcmUserJob -ErrorAction SilentlyContinue if ($deploymentUserIdentifier) { Log-Info -Message (" Found user '{0}' in Active Directory" -f $credentialName) -Type Info -Function "ExecutingAsDeploymentUser" } else { $detail = " Get-ADUser -Identity $credentialName didn't return the $credentialName information. Ensure that $credentialName exist in AD." Log-Info -Message ($detail) -Type Error -Function "ExecutingAsDeploymentUser" $failureReasons += (" Get-ADUser -Identity $credentialName didn't return the $credentialName information. Ensure that $credentialName exist in AD.") } } elseif ($status -eq 'Running') { $detail = " Ensure Get-ADUser -Identity $credentialName will return the results with-in $timeoutSeconds sec." Log-Info -Message ($detail) -Type Error -Function "ExecutingAsDeploymentUser" $failureReasons += (" Ensure Get-ADUser -Identity $credentialName will return the results with-in $timeoutSeconds sec.") Stop-Job -Job $lcmUserJob -ErrorAction Ignore | Out-Null } else { $detail = " Unable to execute Get-ADUser -Identity $credentialName. Ensure that $credentialName exist in AD." Log-Info -Message ($detail) -Type Error -Function "ExecutingAsDeploymentUser" $failureReasons += (" Unable to execute Get-ADUser -Identity $credentialName. Ensure that $credentialName exist in AD.") } Remove-Job -Job $lcmUserJob -ErrorAction Ignore | Out-Null } if ($deploymentUserIdentifier) { if ( (gwmi win32_computersystem).partofdomain ) { $identityReference = $credentialName } else { $identityReference = $deploymentUserIdentifier.SID } # Test whether the AdCredentials user has all access rights to the OU 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" $failureReasons += (" Error while trying to create active directory PS drive. Inner exception: {0}" -f $_) } } $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" $failureReasons += (" 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 {} } } if ($ouAcl) { try { #Verify whether the user has generic all permissions or not. $genericAllPermissions = $ouAcl.Access | Where-Object { ` $_.IdentityReference -match $identityReference -and ` $_.ObjectType -eq [System.Guid]::Empty -and ` $_.InheritedObjectType -eq [System.Guid]::Empty -and ` $_.ActiveDirectoryRights -eq [System.DirectoryServices.ActiveDirectoryRights]::GenericAll } if ($genericAllPermissions) { Log-Info -Message (" AD OU ({0}) has genericAll permissions to the SID {1}." -f $ouPath, $identityReference ) -Type Info -Function "ExecutingAsDeploymentUser" } else { $computerCreateAndDeleteChildPermissions = $ouAcl.Access | Where-Object { ` $_.IdentityReference -match $identityReference -and ` $_.ActiveDirectoryRights -eq [System.DirectoryServices.ActiveDirectoryRights]::CreateChild -bor [System.DirectoryServices.ActiveDirectoryRights]::DeleteChild -and ` $_.ObjectType -eq ([System.Guid]::New('bf967a86-0de6-11d0-a285-00aa003049e2')) } $readPropertyPermissions = $ouAcl.Access | Where-Object { ` $_.IdentityReference -match $identityReference -and ` $_.ActiveDirectoryRights -eq [System.DirectoryServices.ActiveDirectoryRights]::ReadProperty -and ` $_.InheritedObjectType -eq [System.Guid]::Empty -and ` $_.ObjectType -eq [System.Guid]::Empty } $msfveRecoverInformationobjectsPermissions = $ouAcl.Access | Where-Object { ` $_.IdentityReference -match $identityReference -and ` $_.ActiveDirectoryRights -eq [System.DirectoryServices.ActiveDirectoryRights]::GenericAll -and ` $_.ObjectType -eq [System.Guid]::Empty -and ` $_.InheritedObjectType -eq ([System.Guid]::New('ea715d30-8f53-40d0-bd1e-6109186d782c')) } if ($computerCreateAndDeleteChildPermissions) { Log-Info -Message (" For AD OU ({0}) found active directory rights ({1}) and object type ({2}) " -f $ouPath, $computerCreateAndDeleteChildPermissions.ActiveDirectoryRights, $computerCreateAndDeleteChildPermissions.ObjectType ) -Type Info -Function "ExecutingAsDeploymentUser" } else { Log-Info -Message (" Found ACLs for AD OU ({0}), but user ({1})'s didn't have access rights to create/delete computer objects. " -f $ouPath,$credentialName) -Type Error -Function "ExecutingAsDeploymentUser" $failureReasons += ($testContext["LcAdTxt"].CurrentUserMissingCreateAndDeleteComputerObjectPermission -f $adOuPath) } if ($readPropertyPermissions) { Log-Info -Message (" For AD OU ({0}) found active directory rights ({1}) and object type ({2}) " -f $ouPath, $readPropertyPermissions.ActiveDirectoryRights, $readPropertyPermissions.ObjectType ) -Type Info -Function "ExecutingAsDeploymentUser" } else { Log-Info -Message (" Found ACLs for AD OU ({0}), but user ({1})'s didn't have access rights to read AD objects. " -f $ouPath,$credentialName) -Type Error -Function "ExecutingAsDeploymentUser" $failureReasons += ($testContext["LcAdTxt"].CurrentUserMissingReadObjectPermissions -f $adOuPath) } if ($msfveRecoverInformationobjectsPermissions) { Log-Info -Message (" For AD OU ({0}) found active directory rights ({1}) and object type ({2}) " -f $ouPath, $msfveRecoverInformationobjectsPermissions.ActiveDirectoryRights, $msfveRecoverInformationobjectsPermissions.ObjectType ) -Type Info -Function "ExecutingAsDeploymentUser" } else { Log-Info -Message (" Found ACLs for AD OU ({0}), but user ({1})'s didn't have access rights to msFVE-RecoverInformationobjects. " -f $ouPath,$credentialName) -Type Error -Function "ExecutingAsDeploymentUser" $failureReasons += ($testContext["LcAdTxt"].CurrentUserMissingMsfveRecoverInformationobjectsPermissions -f $adOuPath) } } } catch { Log-Info -Message (" Error while trying to get access rules for OU. Inner exception: {0}" -f $_) -Type Error -Function "ExecutingAsDeploymentUser" $failureReasons += (" Error while trying to get access rules for OU. Inner exception: {0}" -f $_) } } } 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 += (" FAILED to look up ACL for AD OU ({0}) and search for user ({1}). Inner exception: {2}" -f $ouPath,$credentialName,$_) } } if ($failureReasons.Count -gt 0) { $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-ADComputerObjects { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [hashtable] $testContext ) $serverParams = @{} if ($testContext["AdServer"]) { $serverParams += @{Server = $testContext["AdServer"]} } if ($testContext["AdCredentials"]) { $serverParams += @{Credential = $testContext["AdCredentials"]} } $adOUPath = $testContext["ADOUPath"] $domainFQDN = $testContext["DomainFQDN"] $seedNode = $ENV:COMPUTERNAME $detailedErrors = @() $testResult = $true #Todo:: Check the domain status for all the nodes. Log-Info -Message (" Validating seednode : {0} is part of a domain or not " -f $seedNode) -Type Info -Function "Test-ADComputerObjects" $domainStatus = (gwmi win32_computersystem).partofdomain if ($domainStatus) { $physicalHosts = @($testContext["PhysicalMachineNames"] | Where-Object { -not [string]::IsNullOrEmpty($_) }) foreach ($physicalHost in $physicalHosts) { try { Log-Info -Message (" Validating computer object : {0} in Domain." -f $physicalHost) -Type Info -Function "Test-ADComputerObjects" $computerObject = Get-ADComputer -SearchBase $adOUPath -Filter "Name -eq '$physicalHost' " @serverParams if ($computerObject -eq $null) { $testResult = $false $error = ("{0} not found in AD under {1} " -f $physicalHost, $adOUPath) Log-Info -Message ($error) -Type Info -Function "Test-ADComputerObjects" $failureReasons += $error } else { Log-Info -Message (" Found the computer object : {0} in Domain." -f $physicalHost) -Type Info -Function "Test-ADComputerObjects" } } catch { $testResult = $false $error = ("Unable to get the computer object {0} from AD.Exception :: {1}" -f $physicalHost, $_.Exception) Log-Info -Message ($error) -Type Info -Function "Test-ADComputerObjects" $failureReasons += $error } } } $failureReason = $failureReasons -join "; " #As of now we added this test for logging purpose. $testResult = $true $adComputerObjectsTestResult = @{ Result = $testResult FailureReasons = $failureReason } return $adComputerObjectsTestResult } function Test-Connectivity { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [hashtable] $testContext ) $connectivityTests = @{ 'RPCEndpointMapper' = @{ 'port' = 135 'target' = $testContext["AdServer"] 'Description' = "RPC Endpoint Mapper (135) connectivity test" } 'LDAP' = @{ 'port' = 389 'target' = $testContext["AdServer"] 'Description' = "LDAP port (389) connectivity test" } 'LDAPGC' = @{ 'port' = 3268 'target' = $testContext["AdServer"] 'Description' = "LDAPGC port (3268) connectivity test" } 'Kpasswd' = @{ 'port' = 464 'target' = $testContext["AdServer"] 'Description' = "kerberos password change port (464) connectivity test" } 'SMB' = @{ 'port' = 445 'target' = $testContext["AdServer"] 'Description' = "SMB (445) connectivity test" } } $testResult = $true $failureReasons =@() foreach ($key in $connectivityTests.Keys) { $probe = $connectivityTests[$key] Log-Info -Message ($probe.Description) -Type Info -Function "Test-Connectivity" try { $result = Test-NetConnection -ComputerName $probe.target -Port $probe.port -WarningAction SilentlyContinue if ($result.TcpTestSucceeded) { Log-Info -Message ("{0} - Successful" -f $probe.Description) -Type Info -Function "Test-Connectivity" } else { Log-Info -Message ("{0} - Failed" -f $probe.Description) -Type Error -Function "Test-Connectivity" $testResult = $false $failureReasons += ("{0} - Failed" -f $probe.Description) } } catch { Log-Info -Message ("{0} - Failed. Exception :: {1}" -f $probe.Description, $_.Exception) -Type Error -Function "Test-Connectivity" $testResult = $false $failureReasons += ("{0} - Failed. Exception :: {1}" -f $probe.Description, $_.Exception) } } $failureReason = $failureReasons -join "; " $connectivityTestResult = @{ Result = $testResult FailureReasons = $failureReason } return $connectivityTestResult } function Test-CauClusterRole { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $HciClusterName ) $ErrorActionPreference = 'Stop' try { Log-Info -Message ("Get CAU cluster role") -Type Info $statusValue = 'FAILURE' $cauClusterRole = Get-CauClusterRole -ClusterName $HciClusterName -ErrorAction SilentlyContinue if ($cauClusterRole) { Log-Info -Message ("CAU cluster role configured") -Type Info $statusValue = 'SUCCESS' $detailedResult = "CAU cluster role configured" } else { $detailedResult = "CAU cluster role not configured" Log-Info -Message ("CAU cluster role not configured") -Type Error } } catch { $detailedResult = 'Test-CauClusterRole failed with an exception : ' + $_ Log-Info -Message (" Test-CauClusterRole Failed. {0}" -f $detailedResult) -Type Error } $result = @{ Status = $statusValue TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $detailedResult } $params = @{ Name = "AzStackHci_ExternalActiveDirectory_Test_CauClusterRole" Title = "Test cau cluster role" DisplayName = "Test cau cluster role" Severity = 'CRITICAL' Description = 'Tests that the cau cluster role is configured or not.' Tags = @{} Remediation = 'https://learn.microsoft.com/en-us/powershell/module/clusterawareupdating/add-cauclusterrole' TargetResourceID = "Test_CauClusterRole" TargetResourceName = "Test_CauClusterRole" TargetResourceType = 'ActiveDirectory' Timestamp = [datetime]::UtcNow Status = $statusValue AdditionalData = $result HealthCheckSource = $ENV:EnvChkrId } return @( New-AzStackHciResultObject @params) } function Test-LcmUserCredentials { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [pscredential] $LcmUserCredentials, [Parameter(Mandatory=$true)] [string] $DomainFQDN ) try { Add-Type -AssemblyName "System.DirectoryServices.AccountManagement" $statusValue = 'FAILURE' $detailedResult = '' $contextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($contextType, $DomainFQDN) # Extract username and password from PSCredential $credentialParts = $LcmUserCredentials.UserName.Split("\\") $username = $credentialParts[$credentialParts.Length-1] $password = $LcmUserCredentials.GetNetworkCredential().Password if ($principalContext.ValidateCredentials($username, $password)) { $statusValue = 'SUCCESS' $detailedResult = 'Validated lcm user credentials.' } else { $detailedResult = 'Invalid lcm user credentials. UserName :: ' + $username } } catch { $detailedResult = 'Test-LcmUserCredentials failed with an exception : ' + $_ Log-Info -Message (" Test_LcmUserCredentials Failed. {0}" -f $detailedResult) -Type Error } $result = @{ Status = $statusValue TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $detailedResult } $params = @{ Name = "AzStackHci_ExternalActiveDirectory_Test_LcmUserCredentials" Title = "Test lcm user credentials" DisplayName = "Test lcm user credentials" Severity = 'CRITICAL' Description = 'Tests that the lcm user credentials are synchronized with the AD ' Tags = @{} Remediation = 'https://aka.ms/hci-envch' TargetResourceID = "Test_LcmUserCredentials" TargetResourceName = "Test_LcmUserCredentials" TargetResourceType = 'ActiveDirectory' Timestamp = [datetime]::UtcNow Status = $statusValue AdditionalData = $result HealthCheckSource = $ENV:EnvChkrId } return @( New-AzStackHciResultObject @params) } 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)] [string] $OperationType = $null, [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 OperationType = $OperationType } $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)] [string] $OperationType = $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 OUs' Tags = @{} Remediation = 'https://aka.ms/hci-envch' 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 # MIInSQYJKoZIhvcNAQcCoIInOjCCJzYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCHgxPZsbT4TUhL # Z6To3O8y0+4ZMNTmHI0/cZK94Pc3Y6CCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghnlMIIZ4QIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK # KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEILwloS5P # 3ZLKH/ttULcNrGO86+dMs3b3nRMn8Mjt7ulmMEIGCisGAQQBgjcCAQwxNDAyoBSA # EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w # DQYJKoZIhvcNAQEBBQAEggEASMD01qyj/tii9r47673fiw3Vf+UoJtzv6LFTESyr # gcsR3jwVVZ5jpH+zTJ9/jSt5X4VX2PwFcnbLeab9IPZ2/psgVDvuIBfLTM4IKDVg # CtI95wgPq97lRvOZPl4Ir2cKOsUnshNaDaqZNGl411q1mNv0OmbLAbfBGm3Hyr0x # puXHg6irxGlmyOFcGJaD42mfpK2/KKFRsNhBAG8F3OWXiHKWV4NQ5wtx9JgD1Uh5 # Nb+QYrTH7RBl8P/XMhcq0i+df2kWeX1Lpasd/9Pdrjtq9ebstCLgs4Rd74Y6Hv/P # Cpo8QnZq/srsnYaVihPra9tg4UbzBHyMcK4+m57BY05rwaGCF5cwgheTBgorBgEE # AYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUD # BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD # ATAxMA0GCWCGSAFlAwQCAQUABCBKTAH/sCF4ILmS0LyVGhWT2mYmMzRbd6gllHgt # CWscyAIGaeexRuPaGBMyMDI2MDUwMzE0MzExMC41ODNaMASAAgH0oIHRpIHOMIHL # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN # aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT # UyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFNlcnZpY2WgghHtMIIHIDCCBQigAwIBAgITMwAAAiJB0vaq/8i1/wABAAAC # IjANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe # Fw0yNjAyMTkxOTM5NTZaFw0yNzA1MTcxOTM5NTZaMIHLMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUw # LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1ueKJukIuUsAAJo/AY5DZRqH7 # bhgv7CWGNlEdbRGoITrdE6Wsn57NaNu1BTdjBbFcv7Rfixte0x+HRvXSqsD+WeSX # /6/y9wE0Mz+xRPTGIY20K7aQDa68OyzVyUeUCypyZC/gW/3ytO/ZOnU9H2ri77kJ # P8ABrqyy1UxX/OseEgvHsj8yikWT0ARtrjWbXMHFzSOo5hQcfUmMXKqWWz6+N0+U # ynhGy1n+doW4WZgpH8Y5W7hpSokWj1M/Lu4wi3o6Dz9vVWukcgUFGjLAl4YZpOha # h7HuiC/alXImMQf8C3A8q/6/1hFoeIZB4UGkywxB/OSTOSsL6+39pDqzM7CgOpf4 # V799kN94yM9uXJI5T/SiA5MdIZIhEW0+bh85RqDh5YW3/oav54RPxw5OPlH64QV6 # KJkl0FIElMVoLNo8UWRQcMD179x7WASjC6LsaNZ7yK0qcESIsL1wiQmdfQBxcqrF # CpIQfnmQFkOp9IyXUWqza8tmpz8E6aXg9b1eiAT3PVTgrOlPi/hYZCfPxX/6jGty # Pjy1CiwOmJamohmSU//COAenfRT2G2HMRUpCX1zs+AmDmdQM1XRab4YSALLAlDzG # CsgI77nnuJjoXAliJmv7NfrvWAcA5KqCUOWQ6kSPt5r28MfKXWJJpSXtFeS/MkDz # Jy/iJRVyHcFy/B+MtwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFFkHwGoDJ5ZbEEiu # 8KstiusqaozQMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud # HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr # BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw # MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw # DgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBiAM+nqrpwG29txSXv # 42o+CsTe2C4boaRfFju9JaWkLTHwq7pknNONL3n+UG3x/B083EKXiFYrAmul7BTH # CGXU63/xRsZ2wj3ZmR0A4d9nf9saCJVm4juPVFBai/oktOOYH2j+1+zM70woN5on # gB/pvy7X8AfY6JB4XPvb80Qz7fY5eddbnwjzg1sZhUPFbbcweWeACINrzqFK62mM # eXKmhtufMraoogJeJXfWY3x4/pbubgENT3+pXT65203CPF9kfdKE7GKAIRYy3xkB # TDvFd8dufjOpCn38nK6qMlVtnBjDhWQG0PM3E/oxBs5UBrI6pBYkmIHtbjifDquH # T+ThaVV7xHc6InoSc3aNzX49JHUgQmuvDdMjLkbYXeA0/1q5IxSg2U+ycZBOvAi3 # udZPKhA5VzODjf/ucu/vFtXrYcRkmGKN3jujaK3/yMZi2Ju5NEL3ISWorwp7RjeZ # g+JMIK0fosuVj+YCm5r64LH/D9QJDAj+XfZaNeFdv90K5A0QRRGP/poB9yTIVjEX # j/uJzp8L4Dd44sAquqDOiHdkLgxfK8nPqpCSWPZ9G+RCPm85o9cAfxENtrSuOwcp # yKzxsRCYCL+PK4+98orit9EVJ/LLoCeG+jLlj0KaD4Qy6sZe4rWMr1brQLosTBZN # wFnXxNjInCWBd0i7is1yTS/4qTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA # AAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVow # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX # 9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1q # UoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8d # q6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byN # pOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2k # rnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4d # Pf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgS # Uei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8 # QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6Cm # gyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF # ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQID # AQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU # KqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1 # GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0 # bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA # QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL # j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p # Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz # LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwU # tj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN # 3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU # 5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5 # KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGy # qVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB6 # 2FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltE # AY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFp # AUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcd # FYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRb # atGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd # VTNYs6FwZvKhggNQMIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg5MDAtMDVFMC1E # OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw # BwYFKw4DAhoDFQC7ycXVZx3bsDpJkr7VucgpksozuKCBgzCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7aFXLjAiGA8y # MDI2MDUwMzA1MTAwNloYDzIwMjYwNTA0MDUxMDA2WjB3MD0GCisGAQQBhFkKBAEx # LzAtMAoCBQDtoVcuAgEAMAoCAQACAjeSAgH/MAcCAQACAhM5MAoCBQDtoqiuAgEA # MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI # AgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAICyoVbsfk2gYvYCk48bJIGhgFfS # KFVqLcD1oCXuw4ZmsUQO23TTO/QU1Zn+BHz9eKaK3axV8+GObQIewpsToujmSps9 # 1jTosQ1Iwq1yKHKwboMjCf1HPiZL8ngWDJykry2EMhGjqy+o3Hc69OZbm1aI9moR # I13oN4jtQZdZL3sTqt6pwPfqwFA3DBecu+uiv0ynq+gFtU9HTdjardpNN4OY1sQA # e1MSbXS0KBXYWk244Ttv20MTGmMXACyiny1lTdRA1Co67pGPzA4h1z/Q/Vg3Y0Ps # FgvWnRxG7oxIqyJewma19SYrajjDYVysNA/HNmieixNCTBNTJ7xUcruzArExggQN # MIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAiJB # 0vaq/8i1/wABAAACIjANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCATF/ggXZkbmvnkNSEEG4tJFmCv # Nz3+eSt1d8TCYKhtHTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIAVgXQEK # BOfGgjNskmDOmbcEIOnHGNwA+QcRufDR5AkTMIGYMIGApH4wfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAIiQdL2qv/Itf8AAQAAAiIwIgQge0BYPU+G # OOQNtThs8JIT4pVZxK+0CdTvxImeg/PVxbcwDQYJKoZIhvcNAQELBQAEggIACHw0 # 8IgheUwnP3Mdz2YMM/AMfBhx7LzuQQ7TUQN8E+GAnlEPKX5YupqjjPcU5bTgUchs # HnRelRiMAPMlu4DwIHftjE/ilGJqVQRjJUiye2ZJy6FO5FqS3uCsBE761QUPs4w1 # 08q0CgLPdRcNBdRjatoQeGl5JE3bP050519WKwwf0vCBUyCPNvOh4HAsr44vPiMd # lo+0+Ipwp77VTVBXQfYVJTJKOvRXr5IV4YcXzirkft09aDQ3ZPHy29nTh4+VW2bu # LqDd/9PuioqsWriMP/anEkniN7x3JUSthkkug2G/EVwvoEN4FOLqck8eKLCPNzB7 # QyXOdMhJf2qLW6sKYZ+Sp8SfrupHcaeADTuIqCAD1OiAWGCRfi4mxiU62gyozE6n # YQSL+x5wdCejEkqd3LbN77V9XdwmX/h5n9uv1D2ovCJL3pIUBbJ0R43Syqsk2huw # HQQ+NJQ0zzj1OKRcPTm1DopqE2NCu4tzC+y31j4QbDE1BNmtcESdwVIngDY/R+Ot # FMi78R5KhAnCDXJ8y9AnuoSvg/n2AEzpkPawNMdep2K4etZZrYDWiN+nQmec4VPS # sDRoesxaQsLQElF96JyxMNumtCMA51ahy/AvLAAnpbgvs0AHRzb8Mc5GeNmqNLon # NZn5dxxJDVdyhnj+YOcCqE6ECZ0VletS4qhwedE= # SIG # End signature block |