AsHciADArtifactsPreCreationTool.psm1
<#############################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # #############################################################> # Script to create AD objects from domain admin context prior to azurestack hci deployment driver execution. # # Requirements: # - Ensure that the following parameters must be unquie in AD per cluster instance # - Azurestack hci lifecycle management user. # - Azurestack hci organization unit name. # - Azurestack hci deployment prefix (must be at the max 8 characters). # - Azurestack hci deployment cluster name. # - Physical nodes objects (if already created) must be available under hci ou computers organization unit. # # Objects to be created: # - Hci Organizational Unit that will be the parent to an organizational unit for each instance and 2 sub organizational units (Computers and Users) with group policy inheritance blocked. # - A user account (under users OU) that will have full control to that organizational unit. # - Computer objects (if provided under Computers OU else all computer objects must be present under Computer OU) # - Security groups (under users OU) # - Azurestack Hci cluster object (under computers OU) # # This script should be run by a user that has domain admin privileges to the domain. # # Parameter Ex: # -AsHciOUName "OU=Hci001,OU=HciDeployments,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com" [Hci001 OU will be created under OU=HciDeployments,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com] # -DomainFQDN "v.masd.stbtest.microsoft.com" # -AsHciClusterName "s-cluster" # # Usage Ex: # # Hci cluster Deployment # # New-HciAdObjectsPreCreation -Deploy -AzureStackLCMUserCredential (get-credential) -AsHciOUName "OU=Hci001,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com" -AsHciPhysicalNodeList @("Physical Machine1", "Physical Machine2") -AsHciDeploymentPrefix "Hci001" -DomainFQDN "v.masd.stbtest.microsoft.com" -AsHciClusterName "s-cluster" # # # Hci cluster Upgrade # New-HciAdObjectsPreCreation -Upgrade -AzureStackLCMUserCredential (get-credential) -AsHciOUName "OU=Hci001,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com" -AsHciPhysicalNodeList @("Physical Machine1", "Physical Machine2") -AsHciDeploymentPrefix "Hci001" -DomainFQDN "v.masd.stbtest.microsoft.com" -AsHciClusterName "s-cluster" # $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop <# .Synopsis Tests hci organization unit. .Parameter AsHciOUPath The hci ou path. #> function Test-AsHciOU { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AsHciOUPath ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop try { if (-not [adsi]::Exists("LDAP://$AsHciOUPath")) { Write-Error "'$AsHciOUPath' does not exist. Exception :: $_" } Write-Verbose "Successfully verified $AsHciOUPath" } catch { Write-Error "Unable to verify organization unit '$AsHciOUPath'. Exception :: $_" } } <# .Synopsis Verifies deployment prefix uniqueness. .Parameter AsHciOUPath The hci ou path. .Parameter AsHciDeploymentPrefix The hci deployment prefix. #> function Test-AsHciDeploymentPrefix { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $AsHciOUPath, [Parameter(Mandatory = $true)] [string] $AsHciDeploymentPrefix ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::continue $opsAdminGroup = "$AsHciDeploymentPrefix-OpsAdmin" $hciGroup = Get-AdGroup -Filter { Name -eq $opsAdminGroup} # Test for unique AsHciDeploymentPrefix if ($hciGroup) { if (-not ($hciGroup.DistinguishedName -match $AsHciOUPath) ) { Write-Error "Deployment prefix '$AsHciDeploymentPrefix' is in use, please provide an unique prefix" } } } <# .Synopsis Creates HCI organization unit in a given AD domain. .Parameter DomainOUPath The AD domain organization unit path .Parameter HciOUName The HCI organizational unit name. #> function New-AsHciOrganizationalUnit { [CmdletBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory = $true)] [string] $DomainOUPath, [Parameter(Mandatory = $true)] [String] $OUName, [Parameter(Mandatory = $false)] [bool] $Rerun =$false ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop $ouPath = Get-ADOrganizationalUnit -SearchBase $DomainOUPath -Filter { Name -eq $OUName } | Select-Object DistinguishedName if ($ouPath) { Write-Error "Hci organizational unit '$OUName' exists under '$DomainOUPath', to continue with '$OUName' OU please remove it and execute the tool." } try { New-ADOrganizationalUnit -Name $OUName -Path $DomainOUPath Write-Verbose "Successfully created $OUName organization unit under '$DomainOUPath' " } catch { Write-Error "Failed to create organization unit. Exception :: $_" } } <# .Synopsis Remove hci organization unit. .Parameter AsHciOUPath The hci ou path. #> function Remove-AsHciOU { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AsHciOUPath ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop try { if ([adsi]::Exists("LDAP://$AsHciOUPath")) { Get-ADOrganizationalUnit -Identity $AsHciOUPath | Set-ADObject -ProtectedFromAccidentalDeletion:$false -PassThru | Remove-ADOrganizationalUnit -Recursive -Confirm:$false Write-Verbose "Successfully deleted $AsHciOUPath" } else { Write-Verbose "OU $AsHciOUPath not found, skipping the remove operation." } } catch { Write-Error "Unable to delete '$AsHciOUPath'. Exception :: $_" } } <# .Synopsis Creates new secruity groups and gMSA accounts if required during update process. .Parameter AsHciLCMUserName The azure stack hci lifecycle management user names .Parameter DeploymentPrefix The hci deployment prefix. .Parameter AsHciOUName The hci ou name. .Parameter DomainFQDN The active directory domain fqdn #> function Update-SecurityGroupsandGMSAAccounts { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $AsHciLCMUserName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $AsHciDeploymentPrefix, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AsHciOUName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $DomainFQDN ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # OU names $hciParentOUPath = $AsHciOUName.Split(",",2)[1] $hciOUName = ($AsHciOUName.Split(",",2)[0]).split("=")[1] $computersOU = "Computers" $usersOU = "Users" # Create Hci organization units $asHciOUPath = Get-ADOrganizationalUnit -SearchBase $hciParentOUPath -Filter { Name -eq $hciOUName } $asHciComputersOUPath = Get-ADOrganizationalUnit -SearchBase $asHciOUPath.DistinguishedName -Filter { Name -eq $computersOU } $asHciUsersOUPath = Get-ADOrganizationalUnit -SearchBase $asHciOUPath.DistinguishedName -Filter { Name -eq $usersOU } # Create security groups New-AsHciSecurityGroup -AsHciUsersOuPath $asHciUsersOUPath -AsHciComputersOuPath $asHciComputersOUPath -DeploymentPrefix $AsHciDeploymentPrefix -AsHciLCMUserName $AsHciLCMUserName -Verbose # Create gMSA accounts New-AsHciGmsaAccount -DomainFQDN $DomainFQDN -DeploymentPrefix $AsHciDeploymentPrefix -AsHciUsersOuPath $AsHciUsersOuPath -Verbose } <# .Synopsis Creates hci lifecycle management users under hci users organization unit path. .Parameter AsHciLCMUserName The azure stack hci lifecycle management user names .Parameter DomainFQDN The active directory domain fqdn .Parameter AsHciLCMUserPassword The azure stack hci user password. .Parameter HciUsersOUPath The hci users organization unit path object. #> function New-AsHciUser { [CmdletBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory = $true)] [String] $AsHciLCMUserName, [Parameter(Mandatory = $true)] [String] $DomainFQDN, [Parameter(Mandatory = $true)] [SecureString] $AsHciUserPassword, [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $AsHciUsersOUPath, [Parameter(Mandatory = $false)] [bool] $Rerun = $false ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop try { $user = Get-ADUser -SearchBase $AsHciUsersOUPath -Filter { Name -eq $AsHciLCMUserName } if ($user -and (-not $Rerun)) { Write-Error "$AsHciLCMUserName exists, please provide an unique username." } if ($user) { Write-verbose "$AsHciLCMUserName exists under $AsHciUsersOUPath, skipping." } else { New-ADUser -Name $AsHciLCMUserName -AccountPassword $AsHciUserPassword -UserPrincipalName "$AsHciLCMUserName@$DomainFQDN" -Enabled $true -PasswordNeverExpires $true -path $AsHciUsersOUPath.DistinguishedName Write-Verbose "Successfully created '$AsHciLCMUserName' under '$AsHciUsersOUPath'" } } catch { if ($_ -match 'The operation failed because UPN value provided for addition/modification is not unique forest-wide') { Write-Error "UserPrincipalName '$AsHciLCMUserName@$DomainFQDN' already exists, please provide a different user name" } elseif ($_ -match 'The specified account already exists') { Write-Error "$AsHciLCMUserName already exists, please provide a different user name" } else { Write-Error "Unable to create $AsHciLCMUserName. Exception :: $_ " } } } <# .Synopsis Grants full access permissions of hci organization unit to hci lifecycle management user. .Parameter AsHciLCMUserName The hci lifecycle management user name. .Parameter AsHciOUPath The hci organization unit path. #> function Grant-HciOuPermissionsToHciLCMUser { [CmdletBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory = $true)] [string] $AsHciLCMUserName, [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $AsHciOUPath ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop try { $ouPath = "AD:\$($AsHciOUPath.DistinguishedName)" $userSecurityIdentifier = Get-ADuser -Identity $AsHciLCMUserName $userSID = [System.Security.Principal.SecurityIdentifier] $userSecurityIdentifier.SID $acl = Get-Acl -Path $ouPath $userIdentityReference = [System.Security.Principal.IdentityReference] $userSID $adRight = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll" $type = [System.Security.AccessControl.AccessControlType] "Allow" $inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "All" $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($userIdentityReference, $adRight, $type, $inheritanceType) $acl.AddAccessRule($Rule) Set-Acl -Path $ouPath -AclObject $acl (Get-Acl $ouPath).Access | Where-Object IdentityReference -eq $userSID Write-Verbose "Successfully granted access permissions '$($AsHciOUPath.DistinguishedName)' to '$AsHciLCMUserName'" } catch { Write-Error "Failed to grant access permissions '$($AsHciOUPath.DistinguishedName)' to '$AsHciLCMUserName'. Error :: $_" } } <# .Synopsis Pre creates computer object in AD under hci computers ou path. .Parameter Machines The list of machines required for hci deployment. .Parameter DomainFQDN The domain fqdn. .Parameter HciComputerOuPath The hci computers ou path. #> function New-MachineObject { [CmdletBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory = $true)] [String[]] $Machines, [Parameter(Mandatory = $true)] [String] $DomainFQDN, [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $AsHciComputerOuPath ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop foreach ($node in $Machines) { try { $computerObject = Get-ADComputer -SearchBase $($AsHciComputerOuPath.DistinguishedName) -Filter {Name -eq $node } if ($computerObject) { Write-Verbose "$node exists in AD, skipping computer object creation." } else { New-ADComputer -Name $node -SAMAccountName $node -DNSHostName "$node.$DomainFQDN" -Path $AsHciComputerOuPath Write-Verbose "Successfully created $node computer object under $($AsHciComputerOuPath.DistinguishedName)" } } catch { if ($_ -match 'The specified account already exists') { Write-Error "$node object already exists, move $node object under '$($AsHciComputerOuPath.DistinguishedName)'" } else { Write-Error "Error :: $_" } } } } <# .Synopsis Enables dynamic updates in the DNS zone corresponding to the domain and gives NC VMs permissions to update it. .Parameter DomainFQDN The domain fqdn. .Parameter DeploymentPrefix The deployment domain prefix. .Parameter OrganizationalUnit The OrganizationalUnit object under which the NC computer objects will be created .Parameter DNSServerIP IP of the DNS Server. #> function Set-DynamicDNSAndConfigureDomainZoneForNC { [CmdletBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory = $true)] [String] $DomainFQDN, [Parameter(Mandatory = $true)] [string] $DeploymentPrefix, [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $OrganizationalUnit, [Parameter(Mandatory=$false)] [string] $DNSServerIP ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop try { $dnsZone = Get-DnsServerZone -Name $DomainFQDN -ComputerName $DNSServerIP if ($dnsZone.DynamicUpdate -eq "NonsecureAndSecure") { Write-Verbose "DNS server is configured to accept dynamic updates from secure and non-secure sources. No further DNS configuration changes are needed." return } Write-Verbose "Configuring DNS server to accept secure dynamic updates. Identities with the right access will be allowed to update DNS entries." Set-DnsServerPrimaryZone -Name $DomainFQDN -ComputerName $DNSServerIP -DynamicUpdate Secure $ncVmNames = @("$DeploymentPrefix-NC01", "$DeploymentPrefix-NC02", "$DeploymentPrefix-NC03") Write-Verbose "Pre-creating the following computer objects for the Network Controller nodes: $ncVmNames." New-MachineObject -Machines $ncVmNames -DomainFQDN $DomainFQDN -AsHciComputerOuPath $OrganizationalUnit -Verbose $domainZoneAcl = Get-Acl "AD:\$($dnsZone.DistinguishedName)" foreach ($ncVm in $ncVmNames) { Write-Verbose "Giving access to $ncVm to allow it to do DNS dynamic updates." $adComputer = Get-ADComputer $ncVm $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $adComputer.SID,'GenericAll','Allow' $domainZoneAcl.AddAccessRule($ace) } Set-Acl -AclObject $domainZoneAcl "AD:\$($dnsZone.DistinguishedName)" Write-Output "List of AD identities with write permissions that are allowed to do DNS dynamic updates to DNS zone: $DomainFQDN." $domainZoneAcl.Access | where -FilterScript {($_.ActiveDirectoryRights -band ` [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite) -eq ` [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite -and ` $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow} ` | select @{n="Identities";e={$_.IdentityReference}} } catch { Write-Error "Failed to configure the DNS server to support dynamic updates. Please enable dynamic updates manually in your DNS server." } } <# .Synopsis Prestage cluster computer object in AD Refer to https://docs.microsoft.com/en-us/windows-server/failover-clustering/prestage-cluster-adds .Parameter OrganizationalUnit The OrganizationalUnit object under which to create the cluster computer object .Parameter ClusterName The cluster name .Example $ou = Get-ADOrganizationalUnit -Filter 'Name -eq "testou"' New-ADPrestagedCluster -OrganizationalUnit $ou -ClusterName 's-cluster' #> function New-PrestagedAsHciCluster { [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true)] [ValidateNotNull()] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $OrganizationalUnit, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $ClusterName ) $ErrorActionPreference = "Stop" Write-Verbose "Checking computer '$ClusterName' under OU '$($OrganizationalUnit.DistinguishedName)' ..." -Verbose $cno = Get-ADComputer -Filter "Name -eq '$ClusterName'" -SearchBase $OrganizationalUnit if ($cno) { Write-Verbose "Found existing computer with name '$ClusterName', skip creation." } else { Write-Verbose "Creating computer '$ClusterName' under OU '$($OrganizationalUnit.DistinguishedName)' ..." -Verbose $cno = New-ADComputer -Name $ClusterName -Description 'Cluster Name Object of HCI deployment' -Path $OrganizationalUnit.DistinguishedName -Enabled $false -PassThru -Verbose } $cno | Set-ADObject -ProtectedFromAccidentalDeletion:$true -Verbose Write-Verbose "Configuring permission for computer '$ClusterName' ..." -Verbose $ouPath = "AD:\$($OrganizationalUnit.DistinguishedName)" $ouAcl = Get-Acl $ouPath $ouAclUpdate = New-Object System.DirectoryServices.ActiveDirectorySecurity foreach ($ace in $ouAcl.Access) { if ($ace.IdentityReference -notlike "*\$ClusterName$") { $ouAclUpdate.AddAccessRule($ace) } } # Refer to https://docs.microsoft.com/en-us/windows/win32/adschema/c-computer $computersObjectType = [System.Guid]::New('bf967a86-0de6-11d0-a285-00aa003049e2') $allObjectType = [System.Guid]::Empty $ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $cno.SID, "CreateChild", "Allow", $computersObjectType, "All" $ace2 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $cno.SID, "ReadProperty", "Allow", $allObjectType, "All" $ouAclUpdate.AddAccessRule($ace1) $ouAclUpdate.AddAccessRule($ace2) $ouAclUpdate | Set-Acl $ouPath -Verbose (Get-Acl $ouPath).Access | Where-Object IdentityReference -like "*\$ClusterName$" Write-Verbose "Finish prestage for cluster '$ClusterName'." -Verbose } <# .Synopsis Creates required security groups for hci deployment. .Parameter AsHciUsersOuPath The hci users organization unit object. .Parameter AsHciComputersOuPath The hci computers organization unit object. .Parameter DeploymentPrefix The hci deployment prefix. .Parameter PhysicalMachines The hci cluster physical machines. .Parameter AsHciLCMUserName The hci lifecycle management username. #> function New-AsHciSecurityGroup { [CmdletBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $AsHciUsersOuPath, [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $AsHciComputersOuPath, [Parameter(Mandatory = $true)] [string] $DeploymentPrefix, [Parameter(Mandatory = $false)] [string[]] $PhysicalMachines, [Parameter(Mandatory = $true)] [string] $AsHciLCMUserName ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Write-Verbose "Creating security groups under '$($AsHciUsersOuPath.DistinguishedName)'" # List of security groups required for hci deployment. $securityGroups = @("$($DeploymentPrefix)-Sto-SG", "$($DeploymentPrefix)-OpsAdmin" ) foreach ($securityGroup in $securityGroups) { try { $adGroup = Get-AdGroup -SearchBase $($AsHciUsersOuPath.DistinguishedName) -Filter { Name -eq $SecurityGroup} if ($adGroup) { Write-Verbose "$securityGroup is present on AD hence skipping." } else { New-ADGroup -Name $SecurityGroup -DisplayName $securityGroup -Description $securityGroup -GroupCategory Security -GroupScope DomainLocal -Path $($AsHciUsersOuPath.DistinguishedName) Write-Verbose "$securityGroup successfully created." } } catch { Write-Error "$SecurityGroup creation failed. Error :: $_ " } } Write-Verbose "Successfully created security groups under '$($AsHciUsersOuPath.DistinguishedName)'" $physicalMachineSecurityGroups = @("$($DeploymentPrefix)-Sto-SG") #Add physical machines to the security groups. foreach ($physicalMachine in $PhysicalMachines) { $machineObject = Get-ADComputer -SearchBase $($AsHciComputersOuPath.DistinguishedName) -Filter {Name -eq $physicalMachine} Add-Membership -IdentityObject $machineObject -SecurityGroupNames $physicalMachineSecurityGroups -AsHciUsersOUPath $($AsHciUsersOuPath.DistinguishedName) } #Add user membership. $AsHciUserSecurityGroups = @("$($AsHciDeploymentPrefix)-OpsAdmin") $userIdentity = Get-ADUser -SearchBase $AsHciUsersOuPath.DistinguishedName -Filter { Name -eq $AsHciLCMUserName } Add-Membership -IdentityObject $userIdentity -SecurityGroupNames $AsHciUserSecurityGroups -AsHciUsersOuPath $AsHciUsersOuPath.DistinguishedName } <# .Synopsis Creates required group managed service accounts for hci deployment. .Parameter DomainFQDN The domain fqdn. .Parameter AsHciUsersOuPath The hci users organization unit object. #> function New-AsHciGmsaAccount { [CmdletBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory = $true)] [String] $DomainFQDN, [Parameter(Mandatory = $true)] [String] $DeploymentPrefix, [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $AsHciUsersOuPath ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop return # List of gMSA accounts, it's membership, principals allowed to retrieve the password and service principal names if any. $gmsaAccounts = @([pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-ECE"; PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG"); ServicePrincipalName=@("$($DeploymentPrefix)/ae3299a9-3e87-4186-bd99-c43c9ae6a571"); MemberOf=@("$($DeploymentPrefix)-EceSG", "$($DeploymentPrefix)-BM-ECESG", "$($DeploymentPrefix)-FsAcl-InfraSG")}, [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-EceSA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG"); ServicePrincipalName=@("$($DeploymentPrefix)/4dde37cc-6ee0-4d75-9444-7061e156507f"); MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-EceSG", "$($DeploymentPrefix)-BM-EceSG")}) # Creating gmsa accounts. foreach ($gmsaAccount in $GmsaAccounts) { try { $gmsaName = $($gmsaAccount.GmsaName) $adGmsaAccount = Get-ADServiceAccount -SearchBase $($AsHciUsersOuPath.DistinguishedName) -Filter {Name -eq $gmsaName} if ($adGmsaAccount) { Write-Verbose "$gmsaName exists hence skipping." } else { if ($($gmsaAccount.ServicePrincipalName).count -ge 1) { New-ADServiceAccount -Name $gmsaName -DNSHostName "$($gmsaAccount.GmsaName).$DomainFQDN" -ServicePrincipalNames $($gmsaAccount.ServicePrincipalName) -PrincipalsAllowedToRetrieveManagedPassword $($gmsaAccount.PrincipalsAllowedToRetrieveManagedPassword) -ManagedPasswordIntervalInDays 1 -KerberosEncryptionType AES256 -Path $($AsHciUsersOuPath.DistinguishedName) } else { New-ADServiceAccount -Name $gmsaName -DNSHostName "$($gmsaAccount.GmsaName).$DomainFQDN" -PrincipalsAllowedToRetrieveManagedPassword $($gmsaAccount.PrincipalsAllowedToRetrieveManagedPassword) -ManagedPasswordIntervalInDays 1 -KerberosEncryptionType AES256 -Path $($AsHciUsersOuPath.DistinguishedName) } Write-Verbose "Successfully created $gmsaName on AD" } $serviceAccountIdentityObject = Get-ADServiceAccount -SearchBase $($AsHciUsersOuPath.DistinguishedName) -Filter {Name -eq $gmsaName} Add-Membership -IdentityObject $serviceAccountIdentityObject -SecurityGroupNames $($gmsaAccount.MemberOf) -AsHciUsersOuPath $($AsHciUsersOuPath.DistinguishedName) } catch { if ($_ -match 'The operation failed because SPN value provided for addition/modification is not unique forest-wide') { Write-Error "SPN '$($gmsaAccount.ServicePrincipalName)' already exists, please remove the SPN and Rerun the tool." } Write-Error "Failed to create $gmsaName or adding it's membership. Error :: $_" } } } <# .Synopsis Helper function to add the membership of an identity object to the security group name. .Parameter IdentityObject The ad identity object. .Parameter SecurityGroupNames The list of security group names. .Parameter HciUsersOuPath The hci users ou path. #> function Add-Membership { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] $IdentityObject, [Parameter(Mandatory = $true)] $SecurityGroupNames, [Parameter(Mandatory = $true)] $AsHciUsersOuPath ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop foreach ($securityGroupName in $SecurityGroupNames) { try { $groupObject = Get-ADGroup -SearchBase $AsHciUsersOuPath -Filter {Name -eq $securityGroupName} if (-not $groupObject) { Write-Error "$securityGroupName is not available." } $isMember = Get-ADGroupMember -Identity $groupObject |Where-Object {$_.Name -eq $($IdentityObject.Name)} if ($isMember) { Write-Verbose "$($IdentityObject.Name) is already a member of $securityGroupName hence skipping" } else { Add-ADGroupMember -Identity $groupObject -Members $IdentityObject Write-Verbose "Finished adding '$($IdentityObject.Name)' as a member of the group '$securityGroupName'." } } catch { Write-Error "Failed to add the $($IdentityObject.Name) to $securityGroupName. Error :: $_" } } } <# .Synopsis Blocks gpo inheritance to Hci ou .Parameter HciOUs List of hci ou #> function Block-GPInheritance { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit[]] $AsHciOUs ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop foreach ($asHciOu in $AsHciOUs) { try { $gpInheritance = Get-GPInheritance -Target $($asHciOu.DistinguishedName) if (-not $gpInheritance.GpoInheritanceBlocked) { $gpInheritance = Set-GPInheritance -Target $($asHciOu.DistinguishedName) -IsBlocked Yes } Write-Verbose "Gpo inheritance blocked for '$($asHciOu.DistinguishedName)', inheritance blocked state is : $($gpInheritance.GpoInheritanceBlocked)" } catch { Write-Error "Failed to block gpo inheritance for '$($asHciOu.DistinguishedName)'. Error :: $_" } } } <# .Synopsis Verfies the below obejcts are unique or not. Lifecycle management user Organizational unit Phyiscal machines Deployment prefix Cluster name .Parameter AzureStackLCMUserCredential Lifecycle management credentails. .Parameter AsHciOUName Organizational unit. .Parameter AsHciPhysicalNodeList Physical machines list. .Parameter AsHciDeploymentPrefix Deployment prefix. .Parameter AsHciClusterName Cluster name. #> function Test-UniqueAdObjects { Param( [PSCredential] $AzureStackLCMUserCredential, [string] $AsHciOUName, [string[]] $AsHciPhysicalNodeList, [string] $AsHciDeploymentPrefix, [string] $AsHciClusterName ) $Errors = New-Object System.Collections.Generic.List[System.Object] $asHciLCMUserName = $AzureStackLCMUserCredential.UserName if ( Get-ADUser -Filter { Name -eq $asHciLCMUserName }) { $Errors.Add(" UserName :: '$asHciLCMUserName'") } if (-not ([string]::IsNullOrWhitespace($AsHciClusterName))) { if (Get-ADComputer -Filter "Name -eq '$AsHciClusterName'") { $Errors.Add(" Cluster Name :: '$AsHciClusterName'") } } foreach ($node in $AsHciPhysicalNodeList) { if (Get-ADComputer -Filter "Name -eq '$node'") { $Errors.Add(" Physical Node :: '$node'") } } $opsAdminGroup = "$AsHciDeploymentPrefix-OpsAdmin" if (Get-AdGroup -Filter { Name -eq $opsAdminGroup} ) { $Errors.Add(" Deployment prefix :: '$AsHciDeploymentPrefix'") } if ($Errors.Count -ge 1) { Write-Warning "Below object/objects are already available on AD please provide unique values and run the tool." foreach ($error in $Errors) { Write-Warning "$error" } Write-Error "AD precreation object failed" } } <# .Synopsis Creates required active directory objects for deployment / upgrade driver execution. .Parameter Deploy Deployment. .Parameter Upgrade Upgrade. .Parameter AzureStackLCMUserCredential Lifecycle management credentails. .Parameter AsHciOUName Organizational unit. .Parameter AsHciPhysicalNodeList Physical machines list. .Parameter AsHciDeploymentPrefix Deployment prefix. .Parameter AsHciClusterName Cluster name. .Parameter DomainFQDN Domain FQDN. .Parameter DNSServerIP DNS server ipaddress, #> function New-AdObjectsForDeployOrUpgrade { Param( [Switch] $Deploy, [Switch] $Upgrade, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCredential] $AzureStackLCMUserCredential, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AsHciOUName, [Parameter(Mandatory = $true)] [ValidateLength(1,8)] [string] $AsHciDeploymentPrefix, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $DomainFQDN, [string[]] $AsHciPhysicalNodeList, [Parameter(Mandatory = $true)] [ValidateLength(1,15)] [string] $AsHciClusterName, [string] $DNSServerIP ) # OU names $hciOUName = ($AsHciOUName.Split(",",2)[0]).split("=")[1] $computersOU = "Computers" $usersOU = "Users" # Create Hci organization units New-AsHciOrganizationalUnit -DomainOUPath $hciParentOUPath -OUName $hciOUName -Verbose $asHciOUPath = Get-ADOrganizationalUnit -SearchBase $hciParentOUPath -Filter { Name -eq $hciOUName } # Create computers OU under $asHciOUPath New-AsHciOrganizationalUnit -DomainOUPath $($asHciOUPath.DistinguishedName) -OUName $computersOU -Verbose $asHciComputersOUPath = Get-ADOrganizationalUnit -SearchBase $asHciOUPath.DistinguishedName -Filter { Name -eq $computersOU } # Create users OU under $asHciOUPath New-AsHciOrganizationalUnit -DomainOUPath $($asHciOUPath.DistinguishedName) -OUName $usersOU -Verbose $asHciUsersOUPath = Get-ADOrganizationalUnit -SearchBase $asHciOUPath.DistinguishedName -Filter { Name -eq $usersOU } # Create Hci lifecycle management user under Hci users OU path $asHciLCMUserName = $AzureStackLCMUserCredential.UserName $asHciLCMUserPassword = $AzureStackLCMUserCredential.Password New-AsHciUser -AsHciLCMUserName $asHciLCMUserName -AsHciUserPassword $asHciLCMUserPassword -DomainFQDN $DomainFQDN -AsHciUsersOUPath $asHciUsersOUPath -Verbose # Grant permissions to hci lifecycle management user Grant-HciOuPermissionsToHciLCMUser -AsHciLCMUserName $asHciLCMUserName -AsHciOUPath $asHciOUPath -Verbose if ($Deploy) { # Pre-create computer objects New-MachineObject -Machines $AsHciPhysicalNodeList -DomainFQDN $DomainFQDN -AsHciComputerOuPath $asHciComputersOUPath -Verbose } elseif ($Upgrade) { # Move cluster and physical machine objects to Hci OU Move-AsHciAdObjetToHciOU -AsHciPhysicalNodeList $AsHciPhysicalNodeList -AsHciClusterName $AsHciClusterName -AsHciComputerOuPath $asHciComputersOUPath -Verbose } # Pre-stage cluster objects New-PrestagedAsHciCluster -OrganizationalUnit $asHciComputersOUPath -ClusterName $AsHciClusterName -Verbose # Create security groups New-AsHciSecurityGroup -AsHciUsersOuPath $asHciUsersOUPath -AsHciComputersOuPath $asHciComputersOUPath -PhysicalMachines $AsHciPhysicalNodeList -DeploymentPrefix $AsHciDeploymentPrefix -AsHciLCMUserName $AsHciLCMUserName -Verbose # Block gpo inheritance to hci ou Block-GPInheritance -AsHciOUs @($asHciOUPath, $asHciUsersOUPath,$asHciComputersOUPath) -Verbose if ($DNSServerIP) { # Configure the DNS to support dynamic updates and give NC VMs permissions to update the domain zone Set-DynamicDNSAndConfigureDomainZoneForNC -DomainFQDN $DomainFQDN -DeploymentPrefix $AsHciDeploymentPrefix -OrganizationalUnit $asHciComputersOUPath -DNSServerIP $DNSServerIP -Verbose } else { Write-Warning "DNSServerIP parameter was not provided, so configuring dynamic DNS updates on the server was skipped. Please make sure that your DNS supports dynamic updates and that Network Controller VMs have access to do DNS updates." } } <# .Synopsis Move existing AD objects to Hci organizational units. E.g., Move the cluster name object, the host computer objects to the Hci Computers OU. .Parameter AsHciPhysicalNodeList Physical machines list. .Parameter AsHciClusterName Cluster name. .Parameter AsHciComputerOuPath The Hci Computers OrganizationalUnit where the AD computer objects should be moved to. #> function Move-AsHciAdObjetToHciOU { Param( [Parameter(Mandatory = $true)] [string[]] $AsHciPhysicalNodeList, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AsHciClusterName, [Parameter(Mandatory=$true)] [ValidateNotNull()] [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $AsHciComputerOuPath ) $AsHciPhysicalNodeList + $AsHciClusterName | ForEach-Object { $computerObjName = $_ # Check existence $adComputer = Get-ADComputer -Filter { Name -eq $computerObjName } -Property ProtectedFromAccidentalDeletion if (-not $adComputer) { Write-Error "Computer object '$computerObjName' not found in AD." } # Check whether it is in target OU if (-not (Get-ADComputer -Filter { Name -eq $computerObjName } -SearchBase $AsHciComputerOuPath)) { # Unprotect the ad object before moving, otherwise access will be denied $isProtected = $adComputer.ProtectedFromAccidentalDeletion if ($isProtected) { Write-Verbose "Unprotect computer object '$computerObjName' before moving" $adComputer | Set-ADObject -ProtectedFromAccidentalDeletion:$false } Write-Verbose "Moving computer object '$computerObjName' to OU '$($AsHciComputerOuPath.DistinguishedName)'" $adComputer = $adComputer | Move-ADObject -TargetPath $AsHciComputerOuPath -PassThru # Reprotect after moving if ($isProtected) { Write-Verbose "Reprotect computer object '$computerObjName' after moving" $adComputer | Set-ADObject -ProtectedFromAccidentalDeletion:$true } } else { Write-Verbose "Computer object '$computerObjName' is already in OU '$($AsHciComputerOuPath.DistinguishedName)'" } } } <# .Synopsis Cmdlet to Create required active directory objects. .Parameter Deploy Deployment. .Parameter Upgrade Upgrade. .Parameter AzureStackLCMUserCredential Lifecycle management credentials. .Parameter AsHciOUName Organizational unit. .Parameter AsHciPhysicalNodeList Physical machines list. .Parameter AsHciDeploymentPrefix Deployment prefix. .Parameter AsHciClusterName Cluster name. .Parameter DomainFQDN Domain FQDN. .Parameter DNSServerIP DNS server ipaddress, #> function New-HciAdObjectsPreCreation { [CmdletBinding( DefaultParameterSetName= 'Deploy' )] Param( [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)] [Switch] $Deploy, [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)] [Switch] $Upgrade, [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)] [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSCredential] $AzureStackLCMUserCredential, [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)] [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)] [validatePattern('^(OU=[^,]+,)+(DC=[^,]+,)*DC=[^,]+$')] [ValidateNotNullOrEmpty()] [string] $AsHciOUName, [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)] [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)] [ValidateNotNullOrEmpty()] [ValidateLength(1,15)] [validatePattern("^[0-9a-z]([0-9a-z\-]{0,61}[0-9a-z])")] [string[]] $AsHciPhysicalNodeList, [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)] [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)] [ValidateLength(1,8)] [validatePattern('^([a-zA-Z])(\-?[a-zA-Z\d])*$')] [string] $AsHciDeploymentPrefix, [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)] [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $DomainFQDN, [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)] [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)] [ValidateNotNullOrEmpty()] [validatePattern('^((?!-)[A-Za-z0-9-]+(?<!-))$')] [string] $AsHciClusterName, [Parameter(ParameterSetName = 'Deploy', Mandatory=$false)] [ValidateNotNullOrEmpty()] [string] $DNSServerIP ) # import required modules $modules = @('ActiveDirectory','GroupPolicy','Kds') foreach ($module in $modules) { Import-Module -Name $module -Verbose:$false -ErrorAction Stop | Out-Null } $hciParentOUPath = $AsHciOUName.Split(",",2)[1] # Test hci parent ou path exists or not." Test-AsHciOU -AsHciOUPath $hciParentOUPath -Verbose Test-AsHciDeploymentPrefix -AsHciOUPath $AsHciOUName -AsHciDeploymentPrefix $AsHciDeploymentPrefix -Verbose # Create AD objects for a fresh HCI deployment. if ($Deploy) { Test-UniqueAdObjects -AzureStackLCMUserCredential $AzureStackLCMUserCredential ` -AsHciOUName $AsHciOUName ` -AsHciPhysicalNodeList $AsHciPhysicalNodeList ` -AsHciClusterName $AsHciClusterName ` -AsHciDeploymentPrefix $AsHciDeploymentPrefix New-AdObjectsForDeployOrUpgrade -Deploy ` -AzureStackLCMUserCredential $AzureStackLCMUserCredential ` -AsHciOUName $AsHciOUName ` -AsHciDeploymentPrefix $AsHciDeploymentPrefix ` -DomainFQDN $DomainFQDN ` -AsHciPhysicalNodeList $AsHciPhysicalNodeList ` -AsHciClusterName $AsHciClusterName ` -DNSServerIP $DNSServerIP } elseif ($Upgrade) { Test-UniqueAdObjects -AzureStackLCMUserCredential $AzureStackLCMUserCredential ` -AsHciOUName $AsHciOUName ` -AsHciDeploymentPrefix $AsHciDeploymentPrefix New-AdObjectsForDeployOrUpgrade -Upgrade ` -AzureStackLCMUserCredential $AzureStackLCMUserCredential ` -AsHciOUName $AsHciOUName ` -AsHciDeploymentPrefix $AsHciDeploymentPrefix ` -DomainFQDN $DomainFQDN ` -AsHciPhysicalNodeList $AsHciPhysicalNodeList ` -AsHciClusterName $AsHciClusterName ` -DNSServerIP $DNSServerIP } else { Write-Error "Invalid operation" } } Export-ModuleMember -Function New-HciAdObjectsPreCreation Export-ModuleMember -Function Remove-AsHciOU Export-ModuleMember -Function Update-SecurityGroupsandGMSAAccounts # SIG # Begin signature block # MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBAstvRzYxcAMBu # e0H4rU1KFaundrhH3Ho9ar3Jz2P71KCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 # esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU # p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 # 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm # WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa # +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq # jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk # mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 # TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 # kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d # hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM # pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh # JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX # UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir # IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 # 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A # Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H # tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJ7qZeFeIYAUp9W0DlmUOk2U # DH6xxkdzumIvi8ngT3XwMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAi2hj3fg4Xp3l/4anHObIMt3OarzYc7jwF5HhyOEsor2DX2vQnqRWTlsh # z9LL+fcInKMfPWbhpggN7L8aUWF0/EBzyma2JjsF6tvieCCKHuFFdp+YnSV8FHyO # EgkZXtV58FTXMKDg4FXFp3CwgyHTuF5nqf4h8VQ3RdNkovx/ltCsKhVsolUhayFR # 6zs8BpJvQRozFXoGCku5qQcnht73axOy475eh5o6K3y3ZMoCyqyzZEAXu1jDpsgz # Xwh2Qv0vJ1gEj6KY5Yx6o3/rWlGW7RkNow9PH/bzue9jFHh0E3+6TRIj4Xq8tiQd # RvjSNRhH2eafrT+a6LBx42aNujoB5qGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC # F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCBErtBQYLTHLyGkxu91Qa9lSuDQvyn/TpGWEIF7jDOCFQIGZVbKF6yW # GBMyMDIzMTIwNDE5MjIzNy44OTlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHqMIIHIDCCBQigAwIBAgITMwAAAdMdMpoXO0AwcwABAAAB0zANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy # MjRaFw0yNDAyMDExOTEyMjRaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQC0jquTN4g1xbhXCc8MV+dOu8Uqc3KbbaWti5vdsAWM # 1D4fVSi+4NWgGtP/BVRYrVj2oVnnMy0eazidQOJ4uUscBMbPHaMxaNpgbRG9FEQR # FncAUptWnI+VPl53PD6MPL0yz8cHC2ZD3weF4w+uMDAGnL36Bkm0srONXvnM9eNv # nG5djopEqiHodWSauRye4uftBR2sTwGHVmxKu0GS4fO87NgbJ4VGzICRyZXw9+Rv # vXMG/jhM11H8AWKzKpn0oMGm1MSMeNvLUWb31HSZekx/NBEtXvmdo75OV030NHgI # XihxYEeSgUIxfbI5OmgMq/VDCQp2r/fy/5NVa3KjCQoNqmmEM6orAJ2XKjYhEJzo # p4nWCcJ970U6rXpBPK4XGNKBFhhLa74TM/ysTFIrEXOJG1fUuXfcdWb0Ex0FAeTT # r6gmmCqreJNejNHffG/VEeF7LNvUquYFRndiCUhgy624rW6ptcnQTiRfE0QL/gLF # 41kA2vZMYzcc16EiYXQQBaF3XAtMduh1dpXqTPPQEO3Ms5/5B/KtjhSspMcPUvRv # b35IWN+q+L+zEwiphmnCGFTuyOMqc5QE0ruGN3Mx0Vv6x/hcOmaXxrHQGpNKI5Pn # 79Yk89AclqU2mXHz1ZHWp+KBc3D6VP7L32JlwxhJx3asa085xv0XPD58MRW1WaGv # aQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFNLHIIa4FAD494z35hvzCmm0415iMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBAYlhYoUQ+4aaQ54MFNfE6Ey8v4rWv+LtD # RSjMM2X9g4uanA9cU7VitdpIPV/zE6v4AEhe/Vng2UAR5qj2SV3sz+fDqN6VLWUZ # sKR0QR2JYXKnFPRVj16ezZyP7zd5H8IsvscEconeX+aRHF0xGGM4tDLrS84vj6Rm # 0bgoWLXWnMTZ5kP4ownGmm0LsmInuu0GKrDZnkeTVmfk8gTTy8d1y3P2IYc2UI4i # JYXCuSaKCuFeO0wqyscpvhGQSno1XAFK3oaybuD1mSoQxT9q77+LAGGQbiSoGlgT # jQQayYsQaPcG1Q4QNwONGqkASCZTbzJlnmkHgkWlKSLTulOailWIY4hS1EZ+w+sX # 0BJ9LcM142h51OlXLMoPLpzHAb6x22ipaAJ5Kf3uyFaOKWw4hnu0zWs+PKPd192n # deK2ogWfaFdfnEvkWDDH2doL+ZA5QBd8Xngs/md3Brnll2BkZ/giZE/fKyolriR3 # aTAWCxFCXKIl/Clu2bbnj9qfVYLpAVQEcPaCfTAf7OZBlXmluETvq1Y/SNhxC6MJ # 1QLCnkXSI//iXYpmRKT783QKRgmo/4ztj3uL9Z7xbbGxISg+P0HTRX15y4TReBbO # 2RFNyCj88gOORk+swT1kaKXUfGB4zjg5XulxSby3uLNxQebE6TE3cAK0+fnY5UpH # aEdlw4e7ijCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN # MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg5MDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBS # x23cMcNB1IQws/LYkRXa7I5JsKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6RhaQjAiGA8yMDIzMTIwNDE0MDEz # OFoYDzIwMjMxMjA1MTQwMTM4WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDpGFpC # AgEAMAcCAQACAhDcMAcCAQACAhNuMAoCBQDpGavCAgEAMDYGCisGAQQBhFkKBAIx # KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI # hvcNAQELBQADggEBACJFbf3RxhZOAf1DPCmUI5IcoDLu3ciNHJP9VJF4RdzVzAhV # 8/pTedvLZvnRtt1OZLymBYmG0kuzfvfJhDJwVJs3kJ+F1Zl3ShvPG30AczyMdEUw # UwcB0/jEFfokkpPBO30agq1F2Aem/4Oe4ES3NvvmyRzOH1Mw5rrpoQO89+T/GKWk # SCINjnc28qugoYRTPG1nI3ir6jHTpd+2C4yn6IEBqSFMDqIsdKw532PEfyqzc3S2 # VVoBRrKB8xlZVw+GssoMFFX6AymAvPDkW6M/aTWDTWewBstSvSpTWYl/nHm0Ad0V # cRKPFKmiq2Nwreh77nAUqVA+YLVaC/YArlNqYvIxggQNMIIECQIBATCBkzB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdMdMpoXO0AwcwABAAAB0zAN # BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G # CSqGSIb3DQEJBDEiBCAIvwBJScGQYUm00vFslw4buUc3jbzn+u2Cxnjyl3BktDCB # +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIJJm9OrE4O5PWA1KaFaztr9uP96r # QgEn+tgGtY3xOqr1MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAHTHTKaFztAMHMAAQAAAdMwIgQgi0a7i0P0ZZFQZFdDqaIQZEw0IjMT # 0YFU3Uu1cK3JoRMwDQYJKoZIhvcNAQELBQAEggIAqPVpToPJnjiLtf6CGm/zUoR1 # 3tSkd8aupIT7prbFxTIrf2IWrBzpQ9ADPqO2qu0Bu2yBlMn1wC4t6SOlffQlG90B # 0RK28/NB3hOHLymGXT4KYsX5MBEU2tiBgq1tASNwk1k5nzXDOsG9NIx4oRzTDlEn # L1l/znraesnlzZ/NmwdIHHojkUTQ8mqFu5NC1AhzbZ0fl3T8YlH1AMIz7wuxSFxX # N1l14nPGC2GFjP02aq3mdX/Z/+TbZr7IWx4JxhLgzaU/jdgfhRq7ix5LyLlGpgwG # QpS6yMqNcy+kgAObfyG4ucAT1Wma3SWlscRBG4eNf2aqyCtAxvhSCyhqo+HMBFA/ # EKeGLQU/E5MM8QyIvecxHnT4kz1U3/uGYtCFry2u0zDSSd+n8jlJsQZPJU5dLCw5 # muwudbDO0QeFQeCVnBNuW1gA0b/H+nZj0DFe5WlFnDrIxb8d6glQijVS+mWgiuiF # 01qPf9hkvhl1ZLW0GCj42rm0RnaAyqYBjIc4dkKx/zygg2Jo8oW3db3vCcW4Qfip # igKWtPosGR7Kgf/iFOB2foMCHnIH+ga2NDqu2ND8xJoI2mFtR8+DCD+wy4eRqoGm # jDaojgQBmqZ5IRx1/JhW1gRewrR2Jhj3HFGNGtFAlyvuu8SrkBMHrKd+tXZRHPmt # I8DBB5whwIbcFevMmMo= # SIG # End signature block |