ADSyncConfig.psm1
<#
.SYNOPSIS Prepares Active Directory configuration for various purposes. .DESCRIPTION AdSyncConfig.psm1 is a Windows PowerShell script module that provides functions that are used to prepare your Active Directory forest and domains for Azure AD Connect Sync features. #> #---------------------------------------------------------- # STATIC VARIABLES #---------------------------------------------------------- # Well known SIDS $selfSid = "S-1-5-10" $enterpriseDomainControllersSid = "S-1-5-9" # Regex variables $distinguishedNameRegex = [Regex] '^(?:(?<cn>CN=(?<name>[^,]*)),)?(?:(?<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?<domain>(?:DC=[^,]+,?)+)$' $defaultADobjProperties = @('UserPrincipalName','ObjectGUID','ObjectSID','mS-DS-ConsistencyGuid','sAMAccountName','objectSid') # Parameter variables $commaSeparator ="," $periodSeparator = "." $colonSeparator = ":" $atSeparator = "@" $aclSeparator = '" "' $inheritanceSubObjectsOnly = 'S' $inheritanceThisAndSubObjects = 'T' $inheritanceNone = 'N' <# .SYNOPSIS Tighten permissions on an AD object that is not otherwise included in any AD protected security group. A typical example is the AD Connect account (MSOL) created by AAD Connect automatically. This account has replicate permissions on all domains, however can be easily compromised as it is not protected. .DESCRIPTION The Set-ADSyncRestrictedPermissions Function will tighten permissions oo the account provided. Tightening permissions involves the following steps: 1. Disable inheritance on the specified object 2. Remove all ACEs on the specific object, except ACEs specific to SELF. We want to keep the default permissions intact when it comes to SELF. 3. Assign these specific permissions: Type Name Access Applies To ============================================================================================= Allow SYSTEM Full Control This object Allow Enterprise Admins Full Control This object Allow Domain Admins Full Control This object Allow Administrators Full Control This object Allow Enterprise Domain Controllers List Contents Read All Properties Read Permissions This object Allow Authenticated Users List Contents Read All Properties Read Permissions This object .PARAMETER ADConnectorAccountDN DistinguishedName of the Active Directory account whose permissions need to be tightened. This is typically the MSOL_nnnnnnnnnn account or a custom domain account that is configured in your AD Connector. .PARAMETER Credential Administrator credential that has the necessary privileges to restrict the permissions on the ADConnectorAccountDN account. This is typically the Enterprise or Domain administrator. Use the fully qualified domain name of the administrator account to avoid account lookup failures. Example: CONTOSO\admin .PARAMETER DisableCredentialValidation When DisableCredentialValidation is used, the function will not check if the credentials provided in -Credential are valid in AD and if the account provided has the necessary privileges to restrict the permissions on the ADConnectorAccountDN account. .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .EXAMPLE Set-ADSyncRestrictedPermissions -ADConnectorAccountDN "CN=TestAccount1,CN=Users,DC=Contoso,DC=com" -Credential $(Get-Credential) #> Function Set-ADSyncRestrictedPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param( [Parameter(Mandatory=$True)] [string] $ADConnectorAccountDN, [Parameter(Mandatory=$True)] [System.Management.Automation.PSCredential] $Credential, [Parameter(Mandatory=$False)] [switch] $DisableCredentialValidation, [string] $TargetForest = $null ) # Function init [string] $functionMsg = "Set-ADSyncRestrictedPermissions:" If (!$DisableCredentialValidation) { # Validate Credential TestCredential $Credential } If ($PSCmdlet.ShouldProcess($ADConnectorAccountDN, "Set restricted permissions")) { $networkCredential = $Credential.GetNetworkCredential() If ($TargetForest) { $path = "LDAP://" + $TargetForest + "/" + $ADConnectorAccountDN } Else { $path = "LDAP://" + $networkCredential.Domain + "/" + $ADConnectorAccountDN } $de = New-Object System.DirectoryServices.DirectoryEntry($path, $Credential.UserName, $networkCredential.Password) $selfName = ConvertSIDtoName $selfSid Try { [System.DirectoryServices.DirectoryEntryConfiguration]$deOptions = $de.get_Options() $deOptions.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl } Catch { Throw "$functionMsg Failure using credential to access Active Directory. Error Details: $($_.Exception.Message)" } Try { Write-Output "$functionMsg Setting Restricted permissions on '$ADConnectorAccountDN'..." # disable inheritance on the object and remove inherited DACLs $de.ObjectSecurity.SetAccessRuleProtection($true, $false); # remove all DACLs on the object except SELF $acl = $de.ObjectSecurity.GetAccessRules($true, $false, [System.Security.Principal.NTAccount]) ForEach ($ace in $acl) { If ($ace.IdentityReference -ne $selfName) { $de.ObjectSecurity.RemoveAccessRule($ace) > $null } } # Add specific DACLs on the object # Add Full Control for SYSTEM $systemSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) $systemDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($systemSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow) $de.ObjectSecurity.AddAccessRule($systemDacl) # Add Full Control for Enterprise Admins $eaSid = GetEnterpriseAdminsSid $Credential $eaDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($eaSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow) $de.ObjectSecurity.AddAccessRule($eaDacl) # Add Full Control for Domain Admins $daSid = GetDomainAdminsSid $Credential $daDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($daSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow) $de.ObjectSecurity.AddAccessRule($daDacl) # Add Full Control for Administrators $adminSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null) $adminDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($adminSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow) $de.ObjectSecurity.AddAccessRule($adminDacl) # Add Generic Read for ENTERPRISE DOMAIN CONTROLLERS $edcSid = New-Object System.Security.Principal.SecurityIdentifier($enterpriseDomainControllersSid) $edcDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($edcSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericRead, [System.Security.AccessControl.AccessControlType]::Allow) $de.ObjectSecurity.AddAccessRule($edcDacl) # Add Generic Read for Authenticated Users $authenticatedUsersSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AuthenticatedUserSid, $null) $authenticatedUsersDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($authenticatedUsersSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericRead, [System.Security.AccessControl.AccessControlType]::Allow) $de.ObjectSecurity.AddAccessRule($authenticatedUsersDacl) $de.CommitChanges() } Catch [Exception] { Throw "$functionMsg Setting Restricted permissions on $ADConnectorAccountDN failed. Error Details: $($_.Exception.Message)" } Finally { If ($de -ne $null) { $de.Dispose() } } } } <# .SYNOPSIS Initialize your Active Directory forest and domain for password hash synchronization. .DESCRIPTION The Set-ADSyncPasswordHashSyncPermissions Function will give required permissions to the AD synchronization account, which include the following: 1. Replicating Directory Changes 2. Replicating Directory Changes All These permissions are given to all domains in the forest. .PARAMETER ADConnectorAccountName The Name of the Active Directory account that will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDomain The Domain of the Active Directory account that will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDN The DistinguishedName of the Active Directory account that will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Set-ADSyncPasswordHashSyncPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' .EXAMPLE Set-ADSyncPasswordHashSyncPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' #> Function Set-ADSyncPasswordHashSyncPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param ( # AD Connector Account Name used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountName, # AD Connector Account Domain used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDomain, # AD Connector Account DistinguishedName used by Azure AD Connect Sync [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDN, # Forest to set the permissions on [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $TargetForest = $null, # Credentials used to configure the permissions [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Resolve AD Connector account identity on AD $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN ` -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $ADConnectorAccountSid = $ADConnectorIdentity.objectSid # Define AD ACL $acls = "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes`"" + " " $acls += "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes All`"" # Set root permissions for all Domains in the Forest $message = "Grant Password Hash Synchronization permissions" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -Inheritance $inheritanceNone -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } <# .SYNOPSIS Initialize your Active Directory forest and domain for password write-back from Azure AD. .DESCRIPTION The Set-ADSyncPasswordWritebackPermissions Function will give required permissions to the AD synchronization account, which include the following: 1. Reset Password on descendant user objects 2. Write Property access on lockoutTime attribute for all descendant user objects 3. Write Property access on pwdLastSet attribute for all descendant user objects These permissions are applied to all domains in the forest. Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects). .PARAMETER ADConnectorAccountName The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDomain The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDN The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER IncludeAdminSDHolders Optional parameter to indicate if AdminSDHolder container should be updated with these permissions .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' .EXAMPLE Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' .EXAMPLE Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders .EXAMPLE Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com' #> Function Set-ADSyncPasswordWritebackPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param ( # AD Connector Account Name used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountName, # AD Connector Account Domain used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDomain, # AD Connector Account DistinguishedName used by Azure AD Connect Sync [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDN, # DistinguishedName of the target AD object to set permissions (optional) [string] $ADobjectDN = $null, # Update permissions on the AdminSdHolders container (optional) [switch] $IncludeAdminSDHolders = $false, # Forest to set the permissions on [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $TargetForest = $null, # Credentials used to configure the permissions [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Resolve AD Connector account identity on AD $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN ` -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $ADConnectorAccountSid = $ADConnectorIdentity.objectSid # Define AD ACL $acls = "`"$ADConnectorAccountSid" + ":CA;Reset Password;user`"" + " " $acls += "`"$ADConnectorAccountSid" + ":WP;lockoutTime;user`"" + " " $acls += "`"$ADConnectorAccountSid" + ":WP;pwdLastSet;user`"" # Check if setting permissions on a AD object only $message = "Grant Password Writeback permissions" If (![string]::IsNullOrEmpty($ADobjectDN)) { # Get target AD Object Write-Verbose "Set-ADSyncPasswordWritebackPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * " $errorMsg = "Set-ADSyncPasswordWritebackPermissions: Unable to find Active Directory object '$ADobjectDN'. " $object = Get-ADSyncADObject $ADobjectDN $errorMsg # Define Inheritance Type $objectInheritanceType = $inheritanceThisAndSubObjects If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS')) { $objectInheritanceType = $inheritanceSubObjectsOnly } # Set AD Permissions on a target AD object Write-Verbose "Set-ADSyncPasswordWritebackPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..." GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential # In case target ADObjectDN is a domain root, set Unexpire Password for the domain # Note: Unexpire Password permissions are only applicable on a domain root object (i.e. domainDNS objectClass) if ($object.objectclass -eq 'domainDNS') { # Define Inheritance Type for Unexpire Password $objectInheritanceType = $inheritanceThisAndSubObjects # Set ACL for Unexpire Password on the domain's root (domainDNS) $acls = "`"$ADConnectorAccountSid" + ":CA;Unexpire Password;`"" $message = "Grant Password Writeback permission for Unexpire Password extended right" GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } Else { # Set root permissions for all Domains in the Forest Write-Verbose "Set-ADSyncPasswordWritebackPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential # Define Inheritance Type for Unexpire Password $objectInheritanceType = $inheritanceThisAndSubObjects # Set ACL for Unexpire Password on all domains in the forest $acls = "`"$ADConnectorAccountSid" + ":CA;Unexpire Password;`"" $message = "Grant Password Writeback permission for Unexpire Password extended right" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -Inheritance $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } <# .SYNOPSIS Initialize your Active Directory forest and domain for Group writeback from Azure AD. .DESCRIPTION The Set-ADSyncUnifiedGroupWritebackPermissions Function will give required permissions to the AD synchronization account, which include the following: 1. Generic Read/Write, Delete, Delete Tree and Create\Delete Child for Group Object types and SubObjects These permissions are applied to all domains in the forest. Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects). In this case, ADobjectDN will be the Distinguished Name of the Container that you desire to link with the GroupWriteback feature. .PARAMETER ADConnectorAccountName The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDomain The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDN The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER includeAdminSDHolders Optional parameter to indicate if AdminSDHolder container should be updated with these permissions .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' .EXAMPLE Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' .EXAMPLE Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders .EXAMPLE Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com' #> Function Set-ADSyncUnifiedGroupWritebackPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param ( # AD Connector Account Name used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountName, # AD Connector Account Domain used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDomain, # AD Connector Account DistinguishedName used by Azure AD Connect Sync [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDN, # DistinguishedName of the target AD object to set permissions (optional) [string] $ADobjectDN = $null, # Update permissions on the AdminSdHolders container (optional) [switch] $IncludeAdminSDHolders = $false, # Forest to set the permissions on [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $TargetForest = $null, # Credentials used to configure the permissions [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Ensure the function is being run as Administrator $isRunningAsAdmin = [bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544") If (!$isRunningAsAdmin) { Write-Error "This cmdlet must be run in an elevated PowerShell window." return } # Resolve AD Connector account identity on AD $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN ` -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $ADConnectorAccountSid = $ADConnectorIdentity.objectSid # Check if setting permissions on a AD object only $message = "Grant Group Writeback permissions" If (![string]::IsNullOrEmpty($ADobjectDN)) { # Get target AD Object Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * " $errorMsg = "Set-ADSyncUnifiedGroupWritebackPermissions: Unable to find Active Directory object '$ADobjectDN'. " $object = Get-ADSyncADObject $ADobjectDN $errorMsg # Set AD Permissions on a target AD object and apply different inheritance flags as necessary If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS')) { # Create/Delete Child group objects applies to the AD object and all children Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$inheritanceThisAndSubObjects'..." $acls = "`"$ADConnectorAccountSid" + ":CCDC;group;`"" GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $inheritanceThisAndSubObjects -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential # Generic Read/Write, Delete object, and Delete tree applies to child inherited group objects of the AD object Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$inheritanceSubObjectsOnly'..." $acls = "`"$ADConnectorAccountSid" + ":GRGWSDDT;;group`"" GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $inheritanceSubObjectsOnly -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } Else { Throw "Group writeback permissions can only be set on a Active Directory Domain, Organizational Unit or Container object." } } Else { # Define AD ACL # Create/Delete Child group objects applies to the AD object and all children $acls = "`"$ADConnectorAccountSid" + ":CCDC;group;`"" + " " # Generic Read/Write, Delete object, and Delete tree applies to child inherited group objects of the AD object $acls += "`"$ADConnectorAccountSid" + ":GRGWSDDT;;group`"" # Set root permissions for all Domains in the Forest Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } <# .SYNOPSIS Initialize your Active Directory forest and domain for mS-DS-ConsistencyGuid feature. .DESCRIPTION The Set-ADSyncMsDsConsistencyGuidPermissions Function will give required permissions to the AD synchronization account, which include the following: 1. Read/Write Property access on mS-DS-ConsistencyGuid attribute for all descendant user and group objects These permissions are applied to all domains in the forest. Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects). .PARAMETER ADConnectorAccountName The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDomain The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDN The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER includeAdminSDHolders Optional parameter to indicate if AdminSDHolder container should be updated with these permissions .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' .EXAMPLE Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' .EXAMPLE Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders .EXAMPLE Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com' #> Function Set-ADSyncMsDsConsistencyGuidPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param( # AD Connector Account Name used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountName, # AD Connector Account Domain used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDomain, # AD Connector Account DistinguishedName used by Azure AD Connect Sync [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDN, # DistinguishedName of the target AD object to set permissions (optional) [string] $ADobjectDN = $null, # Update permissions on the AdminSdHolders container (optional) [switch] $IncludeAdminSDHolders = $false, # Forest to set the permissions on [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $TargetForest = $null, # Credentials used to configure the permissions [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Resolve AD Connector account identity on AD $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN ` -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $ADConnectorAccountSid = $ADConnectorIdentity.objectSid # Define AD ACL $acls = "`"$ADConnectorAccountSid" + ":RPWP;mS-DS-ConsistencyGuid;user`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RPWP;mS-DS-ConsistencyGuid;group`"" # Check if setting permissions on a AD object only $message = "Grant mS-DS-ConsistencyGuid permissions" If (![string]::IsNullOrEmpty($ADobjectDN)) { # Get target AD Object Write-Verbose "Set-ADSyncMsDsConsistencyGuidPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * " $errorMsg = "Set-ADSyncMsDsConsistencyGuidPermissions: Unable to find Active Directory object '$ADobjectDN'. " $object = Get-ADSyncADObject $ADobjectDN $errorMsg # Define Inheritance Type $objectInheritanceType = $inheritanceThisAndSubObjects If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS')) { $objectInheritanceType = $inheritanceSubObjectsOnly } # Set AD Permissions on a target AD object Write-Verbose "Set-ADSyncMsDsConsistencyGuidPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..." GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } Else { # Set root permissions for all Domains in the Forest Write-Verbose "Set-ADSyncMsDsConsistencyGuidPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } <# .SYNOPSIS Initialize your Active Directory forest and domain for Exchange Mail Public Folder feature. .DESCRIPTION The Set-ADSyncExchangeMailPublicFolderPermissions Function will give required permissions to the AD synchronization account, which include the following: 1. Read Property access on all attributes for all descendant publicfolder objects These permissions are applied to all domains in the forest. Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects). .PARAMETER ADConnectorAccountName The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDomain The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDN The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER includeAdminSDHolders Optional parameter to indicate if AdminSDHolder container should be updated with these permissions .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' .EXAMPLE Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' .EXAMPLE Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders .EXAMPLE Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com' #> Function Set-ADSyncExchangeMailPublicFolderPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param( # AD Connector Account Name used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountName, # AD Connector Account Domain used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDomain, # AD Connector Account DistinguishedName used by Azure AD Connect Sync [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDN, # DistinguishedName of the target AD object to set permissions (optional) [string] $ADobjectDN = $null, # Update permissions on the AdminSdHolders container (optional) [switch] $IncludeAdminSDHolders = $false, # Forest to set the permissions on [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $TargetForest = $null, # Credentials used to configure the permissions [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Resolve AD Connector account identity on AD $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN ` -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $ADConnectorAccountSid = $ADConnectorIdentity.objectSid # Define AD ACL $acls = "`"$ADConnectorAccountSid" + ":RP;;publicFolder`"" # Check if setting permissions on a AD object only $message = "Grant Exchange Mail Public Folder permissions" If (![string]::IsNullOrEmpty($ADobjectDN)) { # Get target AD Object Write-Verbose "Set-ADSyncExchangeMailPublicFolderPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * " $errorMsg = "Set-ADSyncExchangeMailPublicFolderPermissions: Unable to find Active Directory object '$ADobjectDN'. " $object = Get-ADSyncADObject $ADobjectDN $errorMsg # Define Inheritance Type $objectInheritanceType = $inheritanceThisAndSubObjects If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS')) { $objectInheritanceType = $inheritanceSubObjectsOnly } # Set AD Permissions on a target AD object Write-Verbose "Set-ADSyncExchangeMailPublicFolderPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..." GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } Else { # Set root permissions for all Domains in the Forest Write-Verbose "Set-ADSyncExchangeMailPublicFolderPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } <#.SYNOPSIS Initialize your Active Directory forest and domain for Exchange Hybrid feature. .DESCRIPTION The Set-ADSyncExchangeHybridPermissions Function will give required permissions to the AD synchronization account, which include the following: 1. Read/Write Property access on all attributes for all descendant user objects 2. Read/Write Property access on all attributes for all descendant inetOrgPerson objects 3. Read/Write Property access on all attributes for all descendant group objects 4. Read/Write Property access on all attributes for all descendant contact objects These permissions are applied to all domains in the forest. Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects). .PARAMETER ADConnectorAccountName The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDomain The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDN The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER includeAdminSDHolders Optional parameter to indicate if AdminSDHolder container should be updated with these permissions .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Set-ADSyncExchangeHybridPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' .EXAMPLE Set-ADSyncExchangeHybridPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' .EXAMPLE Set-ADSyncExchangeHybridPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders .EXAMPLE Set-ADSyncExchangeHybridPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com' #> Function Set-ADSyncExchangeHybridPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param ( # AD Connector Account Name used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountName, # AD Connector Account Domain used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDomain, # AD Connector Account DistinguishedName used by Azure AD Connect Sync [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDN, # DistinguishedName of the target AD object to set permissions (optional) [string] $ADobjectDN = $null, # Update permissions on the AdminSdHolders container (optional) [switch] $IncludeAdminSDHolders = $false, # Forest to set the permissions on [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $TargetForest = $null, # Credentials used to configure the permissions [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Resolve AD Connector account identity on AD $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN ` -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $ADConnectorAccountSid = $ADConnectorIdentity.objectSid # Define AD ACL $acls = "`"$ADConnectorAccountSid" + ":RPWP;;user`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RPWP;;inetOrgPerson`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RPWP;;group`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RPWP;;contact`"" # Check if setting permissions on a AD object only $message = "Grant Exchange Hybrid permissions" If (![string]::IsNullOrEmpty($ADobjectDN)) { # Get target AD Object Write-Verbose "Set-ADSyncExchangeHybridPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * " $errorMsg = "Set-ADSyncExchangeHybridPermissions: Unable to find Active Directory object '$ADobjectDN'. " $object = Get-ADSyncADObject $ADobjectDN $errorMsg # Define Inheritance Type $objectInheritanceType = $inheritanceThisAndSubObjects If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS')) { $objectInheritanceType = $inheritanceSubObjectsOnly } # Set AD Permissions on a target AD object Write-Verbose "Set-ADSyncExchangeHybridPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..." GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } Else { # Set root permissions for all Domains in the Forest Write-Verbose "Set-ADSyncExchangeHybridPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } <# .SYNOPSIS Initialize your Active Directory forest and domain for basic read permissions. .DESCRIPTION The Set-ADSyncBasicReadPermissions Function will give required permissions to the AD synchronization account, which include the following: 1. Read Property access on all attributes for all descendant computer objects 2. Read Property access on all attributes for all descendant device objects 3. Read Property access on all attributes for all descendant foreignsecurityprincipal objects 5. Read Property access on all attributes for all descendant user objects 6. Read Property access on all attributes for all descendant inetOrgPerson objects 7. Read Property access on all attributes for all descendant group objects 8. Read Property access on all attributes for all descendant contact objects These permissions are applied to all domains in the forest. Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects). .PARAMETER ADConnectorAccountName The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDomain The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER ADConnectorAccountDN The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory. .PARAMETER includeAdminSDHolders Optional parameter to indicate if AdminSDHolder container should be updated with these permissions .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Set-ADSyncBasicReadPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' .EXAMPLE Set-ADSyncBasicReadPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' .EXAMPLE Set-ADSyncBasicReadPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders .EXAMPLE Set-ADSyncBasicReadPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com' #> Function Set-ADSyncBasicReadPermissions { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param( # AD Connector Account Name used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountName, # AD Connector Account Domain used by Azure AD Connect Sync [Parameter(ParameterSetName='UserDomain', Mandatory=$True)] [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDomain, # AD Connector Account DistinguishedName used by Azure AD Connect Sync [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $ADConnectorAccountDN, # DistinguishedName of the target AD object to set permissions (optional) [string] $ADobjectDN = $null, # Update permissions on the AdminSdHolders container (optional) [switch] $IncludeAdminSDHolders = $false, # Forest to set the permissions on [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [string] $TargetForest = $null, # Credentials used to configure the permissions [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)] [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Resolve AD Connector account identity on AD $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN ` -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $ADConnectorAccountSid = $ADConnectorIdentity.objectSid # Define AD ACL $acls = "`"$ADConnectorAccountSid" + ":RP;;computer`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RP;;device`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RP;;foreignsecurityprincipal`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RP;;user`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RP;;inetOrgPerson`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RP;;group`"" + " " $acls += "`"$ADConnectorAccountSid" + ":RP;;contact`"" # Check if setting permissions on a AD object only $message = "Grant basic read permissions" If (![string]::IsNullOrEmpty($ADobjectDN)) { # Get target AD Object Write-Verbose "Set-ADSyncBasicReadPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * " $errorMsg = "Set-ADSyncBasicReadPermissions: Unable to find Active Directory object '$ADobjectDN'. " $object = Get-ADSyncADObject $ADobjectDN $errorMsg # Define Inheritance Type $objectInheritanceType = $inheritanceThisAndSubObjects If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS')) { $objectInheritanceType = $inheritanceSubObjectsOnly } # Set AD Permissions on a target AD object Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..." GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential # In case target ADObjectDN is a domain root, set Replicating Directory Changes/All permission for the domain # Note: Replicating Directory Changes/All permissions are only applicable on a domain root object (i.e. domainDNS objectClass) if ($object.objectclass -eq 'domainDNS') { # Set Replicating Directory Changes root permission for all Domains in the Forest. $acls = "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes`"" + " " $acls += "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes All`"" $message += " > replicating directory changes" Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnADobject for 'Replicating Directory Changes' permissions on target object '$ADobjectDN'" GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } Else { # Set root permissions for all Domains in the Forest. Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential # Set Replicating Directory Changes/All root permissions for all Domains in the Forest. # Note: Replicating Directory Changes/All permissions are only applicable on a domain root object (i.e. domainDNS objectClass) $acls = "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes`"" + " " $acls += "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes All`"" $functionMsg = 'Grant permissions on all Domains:' $message += " > replicating directory changes" Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnAllDomains for 'Replicating Directory Changes' permissions on all domain's root" GrantADPermissionsOnAllDomains -ACL $acls -Message $message -Inheritance $inheritanceNone -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } <# .SYNOPSIS Gets AD objects with permission inheritance disabled .DESCRIPTION Searches in AD starting from the SearchBase parameter and returns all objects, filtered by ObjectClass parameter, that have the ACL Inheritance currently disabled. .PARAMETER SearchBase The SearchBase for the LDAP query that can be an AD Domain DistinguishedName or a FQDN .PARAMETER ObjectClass The class of the objects to search that can be '*' (for any object class), 'user', 'group', 'container', etc. By default, this function will search for 'organizationalUnit' object class. .PARAMETER TargetForest Optional parameter to configure a target forest instead of the one used by the current user context. .PARAMETER TargetForestCredential Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest. .EXAMPLE Find objects with disabled inheritance in 'Contoso' domain (by default returns 'organizationalUnit' objects only) Get-ADSyncObjectsWithInheritanceDisabled -SearchBase 'Contoso' .EXAMPLE Find 'user' objects with disabled inheritance in 'Contoso' domain Get-ADSyncObjectsWithInheritanceDisabled -SearchBase 'Contoso' -ObjectClass 'user' .EXAMPLE Find all types of objects with disabled inheritance in a OU Get-ADSyncObjectsWithInheritanceDisabled -SearchBase OU=AzureAD,DC=Contoso,DC=com -ObjectClass '*' #> Function Get-ADSyncObjectsWithInheritanceDisabled { [CmdletBinding()] Param ( [Parameter(ParameterSetName='WithoutCredential', Mandatory=$True,Position=0)] [Parameter(ParameterSetName='WithCredential', Mandatory=$True,Position=0)] [String] $SearchBase, [Parameter(Mandatory=$False,Position=1)] [String] $ObjectClass = 'organizationalUnit', [Parameter(ParameterSetName='WithCredential', Mandatory=$True)] [string] $TargetForest = $null, [Parameter(ParameterSetName='WithCredential', Mandatory=$True)] [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Function init [string] $functionMsg = "Get-ADSyncObjectsWithInheritanceDisabled:" Try { # Init a DirectorySearcher object If ($TargetForest) { $networkCredential = $TargetForestCredential.GetNetworkCredential() $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$TargetForest/$SearchBase", $TargetForestCredential.UserName, $networkCredential.Password) $ldapFilter = "(&(ObjectClass=$ObjectClass))" $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher($directoryEntry, $ldapFilter) $directorySearcher.PageSize = 1000 } Else { $ldapFilter = "(&(ObjectClass=$ObjectClass))" $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$SearchBase", $ldapFilter) $directorySearcher.PageSize = 1000 } } Catch { Throw "$functionMsg Unable to search Active Directory. Error Details: $($_.Exception.Message)" } Try { # Search All Objects Write-Verbose "$functionMsg Searching for objects in AD with LDAP Filter: $ldapFilter" $allADobjects = $directorySearcher.FindAll() } Catch { Throw "$functionMsg Unable to query Active Directory. Error Details: $($_.Exception.Message)" } Finally { If ($directorySearcher -ne $null) { $directorySearcher.Dispose() } If ($directoryEntry -ne $null) { $directoryEntry.Dispose() } } # Check if Inheritance is Disabled Foreach ($object in $allADobjects) { $object = $object.GetDirectoryEntry() Write-Verbose "$functionMsg $($object.DistinguishedName)" If ($object.ObjectSecurity.AreAccessRulesProtected -eq $True) { ResolveADobject -IdentityDN $object.DistinguishedName -IdentityParameterSet 'DistinguishedName' -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential } } } <# .Synopsis Shows permissions of a specified AD object. .DESCRIPTION This function retuns all the AD permissions currently set for a given AD object provided in the parameter -ADobjectDN. The ADobjectDN must be provided in a DistinguishedName format. .PARAMETER TargetForestCredential Optional paramter if the target object is on a different forest than the current user context. .EXAMPLE Show-ADSyncADObjectPermissions -ADobjectDN 'DC=Contoso,DC=com' #> Function Show-ADSyncADObjectPermissions { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [string] $ADobjectDN, [System.Management.Automation.PSCredential] $TargetForestCredential = $null ) # Function init [string] $functionMsg = "Get-ADSyncADObjectPermissions:" # See more on dsacls.exe here: https://ss64.com/nt/dsacls.html $cmd = "dsacls.exe `"$ADobjectDN`"" Write-Verbose "$functionMsg Executing command : $cmd" If ($TargetForestCredential) { $networkCredential = $TargetForestCredential.GetNetworkCredential() $domain = $networkCredential.Domain $user = $networkCredential.UserName $passwd = $networkCredential.Password $passwd = ReplaceReservedCharcters -PreReplace $passwd $result = dsacls.exe "`"$ADobjectDN`"" /domain:"`"$domain"`" /user:"`"$user"`" /passwd:"`"$passwd`"" } Else { $result = dsacls.exe "`"$ADobjectDN`"" } If ($lastexitcode -eq 0) { $result } Else { Throw "$functionMsg $result" } } <# .Synopsis Gets the account name and domain that is configured in each AD Connector .DESCRIPTION This function uses the 'Get-ADSyncConnector' cmdlet that is present in AAD Connect to retrieve from Connectivity Parameters a table showing the AD Connector(s) account. .EXAMPLE Get-ADSyncADConnectorAccount #> Function Get-ADSyncADConnectorAccount { [CmdletBinding()] $cmdlet = 'Get-ADSyncConnector' Try { Get-Command $cmdlet -ErrorAction Stop | Out-Null } Catch { Write-Error "Failure calling '$cmdlet' cmdlet. This function can only be executed with AAD Connect installed." Return $null } $adConnectors = Get-ADSyncConnector | where {$_.ConnectorTypeName -eq 'AD'} $adSyncADConnectorAccount = @() ForEach ($connector in $adConnectors) { $connectorAccount = "" | Select ADConnectorName, ADConnectorForest, ADConnectorAccountName, ADConnectorAccountDomain $connectorAccount.ADConnectorName = $connector.Name $connectorAccount.ADConnectorForest = ($connector.ConnectivityParameters | Where {$_.Name -eq 'forest-name'}).Value $connectorAccount.ADConnectorAccountName = ($connector.ConnectivityParameters | Where {$_.Name -eq 'forest-login-user'}).Value $connectorAccount.ADConnectorAccountDomain = ($connector.ConnectivityParameters | Where {$_.Name -eq 'forest-login-domain'}).Value $adSyncADConnectorAccount += $connectorAccount } $adSyncADConnectorAccount } # Gets an object from Active Directory based on its DN Function Get-ADSyncADObject { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [string] $ADobjectDN, [Parameter(Mandatory=$True,Position=1)] [string] $ErrorMsg ) # Get target AD Object Try { Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * -ErrorAction Stop } Catch [Microsoft.ActiveDirectory.Management.ADReferralException] { $ErrorMsg += "Try logging in with a Domain Administrator account in the same domain as the target object or use TargetForest/TargetForestCredential parameters. " Throw $ErrorMsg += "Error Details: $($_.Exception.Message)" } Catch { Throw $ErrorMsg += "Error Details: $($_.Exception.Message)" } } # Grants permissions to specified ObjectDN Function GrantAcls { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [string] $ObjectDN, [Parameter(Mandatory=$True,Position=1)] [string[]] $ACLs, [Parameter(Mandatory=$True,Position=2)] [char] $InheritFlag, [System.Management.Automation.PSCredential] $TargetForestCredential ) # See more on dsacls.exe here: https://ss64.com/nt/dsacls.html If ($TargetForestCredential) { $networkCredential = $TargetForestCredential.GetNetworkCredential() $domain = $networkCredential.Domain $user = $networkCredential.UserName $passwd = $networkCredential.Password If (!$domain) { Write-Error "You must enter your Administrator credentials in a domain.com\username format." return } $passwd = ReplaceReservedCharcters -PreReplace $passwd $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs /I:$InheritFlag /domain:`"$domain`" /user:`"$user`"" $cmdExecute = $cmd + " /passwd:`"$passwd`"" $cmdTrace = $cmd + " /passwd:`"********`"" Write-Verbose "Grant ACLs : Executing command : $cmdTrace" $result = Invoke-Expression $cmdExecute } Else { $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs /I:$InheritFlag" Write-Verbose "Grant ACLs : Executing command : $cmd" $result = Invoke-Expression $cmd } If ($lastexitcode -eq 0) { $result Write-Warning "You will need to perform a full import to include any objects that may have come in scope as a result of this operation." } Else { $noExchangeShemaError = $result | Where {$_ -like "*No GUID Found for publicFolder*"} If (($noExchangeShemaError | Measure-Object).Count -gt 0) { Write-Error "AD Schema for Exchange not present : $result" } Else { Write-Error "$result" } } } # Grants permissions to specified ObjectDN Function GrantAclsNoInheritance { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [string] $ObjectDN, [Parameter(Mandatory=$True,Position=1)] [string[]] $ACLs, [System.Management.Automation.PSCredential] $TargetForestCredential ) # See more on dsacls.exe here: https://ss64.com/nt/dsacls.html If ($TargetForestCredential) { $networkCredential = $TargetForestCredential.GetNetworkCredential() $domain = $networkCredential.Domain $user = $networkCredential.UserName $passwd = $networkCredential.Password If (!$domain) { Write-Error "You must enter your Administrator credentials in a domain.com\username format." return } $passwd = ReplaceReservedCharcters -PreReplace $passwd $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs /domain:`"$domain`" /user:`"$user`"" $cmdExecute = $cmd + " /passwd:`"$passwd`"" $cmdTrace = $cmd + " /passwd:`"********`"" Write-Verbose "Grant ACLs without Inheritance : Executing command : $cmdTrace" $result = Invoke-Expression $cmdExecute } Else { $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs" Write-Verbose "Grant ACLs without Inheritance : Executing command : $cmd" $result = Invoke-Expression $cmd } If ($lastexitcode -eq 0) { $result Write-Warning "You will need to perform a full import to include any objects that may have come in scope as a result of this operation." } Else { Write-Error "$result" } } # Converts a FQDN to domain DistinguishedName Function ConvertFQDNtoDN { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [String] $FQDN ) # Init of variable is required to prevent use of a session variable. $dn = "" # Convert each period separated domain name into a DistinguishedName ForEach ($domain in $($FQDN.Split($periodSeparator))) { $dn = $dn + $connector + "DC=" + $domain $connector = $commaSeparator } Return $dn } # Converts a domain DistinguishedName to FQDN Function ConvertDNtoFQDN { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [String] $DN ) # Init of variable is required to prevent use of a session variable. $fqdn = "" # Convert each commma separated domain into a FQDN ForEach ($domain in $($DN -split $commaSeparator)) { $fqdn += "$($domain.Substring(3))." } # Remove the last dot $fqdn = $fqdn.Substring(0,$fqdn.Length -1) Return $fqdn } # Get list of Domains in the Forest Function GetADDomains { [CmdletBinding()] Param( [String] $TargetForest, [System.Management.Automation.PSCredential] $TargetForestCredential ) # Function init [string] $functionMsg = "Get AD Domains:" Try { If ($TargetForestCredential) { Write-Verbose "$functionMsg Retrieving list of Domains in Forest '$TargetForest'..." $forest = Get-ADForest -Identity $TargetForest -Credential $TargetForestCredential -ErrorAction Stop } Else { Write-Verbose "$functionMsg Retrieving list of Domains in the Forest..." $forest = Get-ADForest -ErrorAction Stop } } Catch { Throw "$functionMsg Unable to get list of Domains in the Forest. Exception Details: $($_.Exception.Message)" } Return $($forest.Domains) } Function ResolveADobject { [CmdletBinding()] Param ( [string] $IdentityName, [string] $IdentityDomain, [string] $IdentityDN, [string] $IdentityParameterSet, [switch] $ReturnADobject, [string] $TargetForest, [System.Management.Automation.PSCredential] $TargetForestCredential ) # Check if AD PowerShell Module is installed ImportADmodule # Init [string] $functionMsg = "Resolve AD object:" Write-Verbose "ParameterSetName: $IdentityParameterSet" switch ($IdentityParameterSet) { {($_ -eq 'UserDomain') -or ($_ -eq 'UserDomainWithCredential')} { # Search for AD object using object name and target domain Write-Verbose "$functionMsg Resolving AD object '$IdentityName' in Domain '$IdentityDomain'..." Try { If ($TargetForestCredential) { $targetDomain = Get-ADDomain -Identity $IdentityDomain -Credential $TargetForestCredential -ErrorAction Stop } Else { $targetDomain = Get-ADDomain -Identity $IdentityDomain -ErrorAction Stop } } Catch { Throw "$functionMsg Unable to find Domain '$IdentityDomain' in AD Forest. Exception Details: $($_.Exception.Message)" } $ldapFilter = "(|(Name=$IdentityName)(sAMAccountName=$IdentityName))" $targetDomainDNSname = $targetDomain.DNSRoot Write-Verbose "$functionMsg LDAP Filter to search object: $ldapFilter" Write-Verbose "$functionMsg Target Domain to search object: $targetDomainDNSname" Try { If ($TargetForestCredential) { $adObject = Get-ADObject -LDAPFilter $ldapFilter -Server $targetDomainDNSname -Properties $defaultADobjProperties -Credential $TargetForestCredential -ErrorAction Stop } Else { $adObject = Get-ADObject -LDAPFilter $ldapFilter -Server $targetDomainDNSname -Properties $defaultADobjProperties -ErrorAction Stop } } Catch { Throw "$functionMsg Unable to get object '$IdentityName' in AD Forest. Exception Details: $($_.Exception.Message)" } } {($_ -eq 'DistinguishedName') -or ($_ -eq 'DistinguishedNameWithCredential')} { # Search for AD object using DistinguishedName against an AD Global Catalog (AD Forest wide search) Write-Verbose "$functionMsg Resolving AD object DN $IdentityDN..." # Encode reserved characters to Hex values $IdentityDN = Convert-DNtoHex $IdentityDN If ($IdentityDN -match $distinguishedNameRegex) { Write-Verbose "$functionMsg Matches in DistinguishedName input: $($Matches.Values | %{"$_;"})" $IdentityDomainDN = $Matches.domain $IdentityName = $Matches.name Try { # Get an array of Domain Controllers running Global Catalog service $globalCatalogSrv = @(Get-ADDomainController -Discover -DomainName $TargetForest -Service GlobalCatalog -ErrorAction Stop) } Catch { Throw "$functionMsg Unable to find a Global Catalog DC in AD. Exception Details: $($_.Exception.Message)" } If (($globalCatalogSrv | Measure-Object).Count -gt 0) { # Pick the first DC in the array and add the GC port $globalCatalogHost = [string] $globalCatalogSrv[0].HostName[0].ToString() + ":3268" Write-Verbose "$functionMsg Target Global Catalog Server to search object: $globalCatalogHost" } Else { Throw "$functionMsg Could not find any available Global Catalog DC in AD." } Try { # Get the AD object based on the DistinguishedName from a GC server in AD If ($TargetForestCredential) { $adObject = Get-ADObject -Identity $IdentityDN -Server $globalCatalogHost -Properties $defaultADobjProperties -Credential $TargetForestCredential -ErrorAction Stop } Else { $adObject = Get-ADObject -Identity $IdentityDN -Server $globalCatalogHost -Properties $defaultADobjProperties -ErrorAction Stop } } Catch { Throw "$functionMsg Unable to get object '$IdentityDN' in AD Forest. Exception Details: $($_.Exception.Message)" } # If going to return Domain\Username, need to get the AD Domain object If (-not $ReturnADobject) { $IdentityDomain = ConvertDNtoFQDN $IdentityDomainDN Try { Write-Verbose "$functionMsg Resolving AD Domain '$IdentityDomain'..." If ($TargetForestCredential) { $targetDomain = Get-ADDomain -Identity $IdentityDomain -Credential $TargetForestCredential -ErrorAction Stop } Else { $targetDomain = Get-ADDomain -Identity $IdentityDomain -ErrorAction Stop } } Catch { Throw "$functionMsg Unable to find Domain '$IdentityDomain' in AD Forest. Exception Details: $($_.Exception.Message)" } } } Else { Throw "$functionMsg Cannot validate argument on parameter 'ADConnectorAccountDN'. The argument '$IdentityDN' does not match a DistinguishedName format." } } } # If found, return the identity If ($adObject -ne $null) { If ($ReturnADobject) { # Return AD object Write-Verbose "$functionMsg Returning AD object: $($adObject.Name)" Return $adObject } Else { # Get the domain NetBIOS name and return Domain\Username $targetDomainNetBIOS = $targetDomain.NetBIOSName $targetUsername = $adObject.sAMAccountName Write-Verbose "$functionMsg Returning NetBIOS Domain name: $targetDomainNetBIOS" Write-Verbose "$functionMsg Returning Username: $targetUsername" $identity = [String] $targetDomainNetBIOS + "\" + $targetUsername Return $identity } } Else { Throw "$functionMsg Unable to find '$IdentityName' in AD Forest." } } # Sets permissions on a target AD object Function GrantADPermissionsOnADobject { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param ( # AD permission ACEs to set [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [String] $ACL, # DistinguishedName of the target AD object to set permissions [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [String] $ADobjectDN, # Permissions description message for console output [String] $Message = "Grant Active Directory Permissions", # Set permission Inheritance for AD objects - Defaults to 'This object and subobjects' [String] $InheritanceType = $inheritanceThisAndSubObjects, [String] $TargetForest, [System.Management.Automation.PSCredential] $TargetForestCredential ) # Function init ImportADmodule [string] $functionMsg = "Grant permissions on AD object:" Write-Verbose "$functionMsg AD permissions ACEs to add: $ACL" # Search for AD object using DistinguishedName $targetADObj = ResolveADobject -IdentityDN $ADobjectDN -IdentityParameterSet 'DistinguishedName' -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential $targetADObjClass = $targetADObj.ObjectClass Write-Verbose "$functionMsg Target AD Object Class: $targetADObjClass" # Set inheritance type $Inheritance = $inheritanceType If ($targetADObjClass -eq 'organizationalUnit' -or $targetADObjClass -eq 'container' -or $targetADObjClass -eq 'domainDNS') { # Set permissions for standard object types $finalACL = $ACL } Else { # Adapt ACL to target the specific AD object $ACEarray = $ACL -split $aclSeparator $targetADObjACL = @() # For each ACE, check if applies to an ObjectClass and if the ObjectClass matches the target AD object ForEach ($ace in $ACEarray) { $indexOfObjClass = $ace.LastIndexOf(";") If ($indexOfObjClass -lt 0) { # No ObjectClass defined in ACE - applies to all objects $targetADObjACL += $ace } Else { # Specific ObjectClass defined in ACE - applies only if the same as the target object $aceTargetClass = $ace.Substring($indexOfObjClass) If ($aceTargetClass.Contains($targetADObjClass)) { # Take this ACE but remove the InheritedObjectType since we will apply ACL to a non-container object $targetADObjACL += $ace -replace ";$targetADObjClass", "" } } } # Finalize the filtered ACEs for the target AD object If (($targetADObjACL | Measure-Object).Count -eq 0) { Throw "$functionMsg Permissions are not applicable to AD ObjectClass '$targetADObjClass': $ACL" } Else { # Glue all the ACEs in a string back again separated by $aclSeparator $finalACL = $null for ($i = 0; $i -lt $targetADObjACL.Count-1; $i++) { $finalACL += [String] $targetADObjACL[$i]+ $aclSeparator } $finalACL += [String] $targetADObjACL[$targetADObjACL.Count-1] } # Enclose all the ACEs in double quotes If ($finalACL[0] -ne '"') { $finalACL = '"' + $finalACL } If ($finalACL[$finalACL.Length-1] -ne '"') { $finalACL = $finalACL + '"' } Write-Verbose "$functionMsg AD permissions ACEs for target AD object: $finalACL" } # Set AD Permissions on a target AD object If ($PSCmdlet.ShouldProcess($targetADObj.Name, $Message)) { Write-Output "$functionMsg Setting permissions on AD object '$($targetADObj.DistinguishedName)'..." GrantAcls $targetADObj.DistinguishedName $finalACL $Inheritance -TargetForestCredential $TargetForestCredential } Else { Write-Verbose "$functionMsg Operation canceled." } } # Sets permissions on all Domains in the Forest Function GrantADPermissionsOnAllDomains { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")] Param ( # AD permission ACEs to set [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [String] $ACL, # Update permissions on the AdminSdHolders container [Switch] $IncludeAdminSDHolders, # Permission Inheritance [String] $Inheritance = $inheritanceSubObjectsOnly, # Permissions description message for console output [String] $Message = "Grant Active Directory Permissions", [String] $TargetForest, [System.Management.Automation.PSCredential] $TargetForestCredential ) # Function init ImportADmodule [string] $functionMsg = "Grant permissions on all Domains:" Write-Verbose "$functionMsg AD permissions ACEs to add: $ACL" # Get list of all Domains in the Forest $domains = GetADDomains -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential # Set root permissions for all Domains in the Forest ForEach($domain in $domains) { If ($PSCmdlet.ShouldProcess($domain, $Message)) { $domainDN = ConvertFQDNtoDN -FQDN $domain Write-Output "$functionMsg AD Domain '$domainDN'..." If ($Inheritance -eq $inheritanceNone) { GrantAclsNoInheritance $domainDN $ACL -TargetForestCredential $TargetForestCredential } Else { GrantAcls $domainDN $ACL $Inheritance -TargetForestCredential $TargetForestCredential } # Check if setting permissions on AdminSdHolder container If ($IncludeAdminSDHolders) { Try { Write-Output "$functionMsg Setting permissions on AdminSDHolder container of '$domainDN'" If ($TargetForestCredential) { $adminSDHolderDN = (Get-ADObject -Server $domain -Filter "Name -like 'adminsdholder'" -Credential $TargetForestCredential).DistinguishedName } Else { $adminSDHolderDN = (Get-ADObject -Server $domain -Filter "Name -like 'adminsdholder'").DistinguishedName } } Catch { Throw "$functionMsg Unable to get AdminSDHolder container of '$domainDN'. Exception details: $($_.Exception)" } # Setting permissions on AdminSdHolder container GrantAcls $adminSDHolderDN $ACL $Inheritance -TargetForestCredential $TargetForestCredential } } Else { Write-Verbose "$functionMsg Operation canceled." } } } Function TestCredential { [CmdletBinding()] Param( [Parameter(Mandatory=$True)] [System.Management.Automation.PSCredential] $Credential ) # Function init [string] $functionMsg = "Test Credential:" $networkCredential = $Credential.GetNetworkCredential() If ($networkCredential.UserName.Contains("@") -or $networkCredential.Domain.ToString() -eq "") { Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Credential should use the fully qualified domain name of the administrator account. Example: CONTOSO\admin" } Try { # $Credential.UserName == FQDN\Username, $networkCredential.UserName == just the Username portion. We need to use the FQDN from here $dc = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $networkCredential.Domain, $Credential.UserName, $networkCredential.Password) -ErrorAction Stop } Catch { Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Unable to use credentials to access AD. Error Details: $($_.Exception.Message)" } Try { # Validating credential $credentialValid = $false $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($dc) $enterpriseAdminsSid = GetEnterpriseAdminsSid $Credential $domainAdminsSid = GetDomainAdminsSid $Credential $domainDE = $domain.GetDirectoryEntry() $searchFilter = "(samAccountName=$($networkCredential.UserName))" $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher($domainDE, $searchFilter) $searchResult = $directorySearcher.FindOne() If ($searchResult -eq $null) { Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Admin account '$($Credential.UserName)' could not be found." } $adminDE = $searchResult.GetDirectoryEntry() [string[]] $propertyName = @("tokenGroups") $adminDE.RefreshCache($propertyName) ForEach ($resultBytes in $adminDE.Properties["tokenGroups"]) { $sid = New-Object System.Security.Principal.SecurityIdentifier($resultBytes, 0) If ($sid.Equals($enterpriseAdminsSid) -or $sid.Equals($domainAdminsSid)) { $credentialValid = $true Break } } If (!$credentialValid) { Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Enterprise Admin or Domain Admin credential is required to restrict permissions on the account." } } Catch [Exception] { Throw "$functionMsg Validating credential parameter failed. Error Details: $($_.Exception.Message)" } Finally { # Clean up memory If ($domain -ne $null) { $domain.Dispose() } If ($domainDE -ne $null) { $domainDE.Dispose() } If ($directorySearcher -ne $null) { $directorySearcher.Dispose() } If ($adminDE -ne $null) { $adminDE.Dispose() } } } Function GetEnterpriseAdminsSid { [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [System.Management.Automation.PSCredential] $Credential ) $networkCredential = $Credential.GetNetworkCredential() $dc = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $networkCredential.Domain, $Credential.UserName, $networkCredential.Password) try { $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($dc) try { $de = $domain.Forest.RootDomain.GetDirectoryEntry() $rootDomainSidInBytes = $de.Properties["ObjectSID"].Value $domainSid = New-Object System.Security.Principal.SecurityIdentifier($rootDomainSidInBytes, 0) $eaSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $domainSid) return $eaSid } finally { if ($de -ne $null) { $de.Dispose() } } } finally { if ($domain -ne $null) { $domain.Dispose() } } } Function GetDomainAdminsSid { [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [System.Management.Automation.PSCredential] $Credential ) $networkCredential = $Credential.GetNetworkCredential() $dc = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $networkCredential.Domain, $Credential.UserName, $networkCredential.Password) try { $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($dc) try { $de = $domain.GetDirectoryEntry() $domainSidInBytes = $de.Properties["ObjectSID"].Value $domainSid = New-Object System.Security.Principal.SecurityIdentifier($domainSidInBytes, 0) $domainAdminsSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainAdminsSid, $domainSid) return $domainAdminsSid } finally { if ($de -ne $null) { $de.Dispose() } } } finally { if ($domain -ne $null) { $domain.Dispose() } } } # Convert SIDs to readable names Function ConvertSIDtoName { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [string] $SID ) $ID = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $sid $User = $ID.Translate([System.Security.Principal.NTAccount]) $User.Value } # Confirm if ActiveDirectory PowerShell Module is present and load it Function ImportADmodule { [CmdletBinding()] Param() If (-not (Get-Module ActiveDirectory)) { Try { # Load ActiveDirectory module Import-Module ActiveDirectory -ErrorAction Stop } Catch { Throw "Import-Module : Unable to import ActiveDirectory PowerShell Module. Run 'Install-WindowsFeature RSAT-AD-Tools' to install Active Directory RSAT." } } } # Encode reserved characters in DistinguishedName to Hexadecimal values based on MSDN documentation: # The following table lists reserved characters that cannot be used in an attribute value without being escaped. # From: https://msdn.microsoft.com/en-us/windows/desktop/aa366101 Function Convert-DNtoHex { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [String] $DN ) $encodedDN = $DN -replace '\\#','\23' ` -replace '\\,','\2C' ` -replace '\\"','\22' ` -replace '\\<','\3C' ` -replace '\\>','\3E' ` -replace '\\;','\3B' ` -replace '\\=','\3D' ` -replace '\\/','\2F' return $encodedDN } # Replace reserved characters for command line arguments Function ReplaceReservedCharcters { [CmdletBinding()] Param ( [Parameter(Mandatory=$True,Position=0)] [String] $PreReplace ) $postReplace = $PreReplace -replace '\"','\"' return $postReplace } # Export ADSyncConfig Module Functions Export-ModuleMember -Function Set-ADSyncRestrictedPermissions Export-ModuleMember -Function Set-ADSyncPasswordHashSyncPermissions Export-ModuleMember -Function Set-ADSyncExchangeHybridPermissions Export-ModuleMember -Function Set-ADSyncMsDsConsistencyGuidPermissions Export-ModuleMember -Function Set-ADSyncPasswordWritebackPermissions Export-ModuleMember -Function Set-ADSyncUnifiedGroupWritebackPermissions Export-ModuleMember -Function Set-ADSyncExchangeMailPublicFolderPermissions Export-ModuleMember -Function Set-ADSyncBasicReadPermissions Export-ModuleMember -Function Get-ADSyncObjectsWithInheritanceDisabled Export-ModuleMember -Function Get-ADSyncADConnectorAccount Export-ModuleMember -Function Show-ADSyncADObjectPermissions # SIG # Begin signature block # MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC044O1QrO/mXzo # vfAGyyj0C1vNPHfykaZ23vytrGQ046CCDY0wggYLMIID86ADAgECAhMzAAACXqZD # 7dFf8ftHAAAAAAJeMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzA5WhcNMjIwOTAxMTgzMzA5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCzfDfI9jKbw9bCSGnQicvizL4xwig71vYg8uDRqmJJCfJ2x/37vUd/AMAvd459 # ldcgTp5HYW5fgEZEqxomTjtNppHLXpG0WdZyFEInEXxd7ElQ24uhiUYLaObhvBKp # ovPuq/7KgaESBqK1EDBQIXU8oLHK8Ogge0+u8LzyxqU7vz9L0b6eup5U3xXsd6Ch # mWLxeuOMbEuQoUBJvIVSCCWfvo68kARAbL6Ej8RJsgoRMom3/AYvJxOecqA8r3ot # vu0rsexkJXT4YziXA+f+7vsS/GqglfmkuV9pOVMu0iTZ3vejRnOUAxF7kkQ4DOHo # sgij+ZtE1k61huu20DNorfAJAgMBAAGjggGKMIIBhjArBgNVHSUEJDAiBgorBgEE # AYI3TBMBBgorBgEEAYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU9DkzBncOLXhk # OEiKpkDl6LSCzBgwUAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzMxMTArNDY3NDk4MB8G # A1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWG # Q2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BD # QTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAC # hkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNp # Z1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B # AQsFAAOCAgEAYd4ugkdgOHoz39d8bKUzw0Z9q0BW8Nn6J7+Q7Tszoj9j4PUzxYFP # lAZmi8WhH/tA+BRjSrxrCEczjqRkvg81nGB6a2Kcxg1H84Q/jY2u/xhPKzIoMEqA # GokCY1bd53Ohae88Wwo/iQ+fL0QVL9RWQ9Hf0/6+LOmAF9YOCiMP+rSImg6w5448 # bzGClo2/TTaVPL8XoyDzZFP96PInlqkhyqd7i5+z1z30EKxfuY6Vw7bqM2Jdvh8H # ozjz1DpVbGkgS2bNOoJWWs4ORXiME+8NJSqND04F00gKbH5cof235y0TTfT8nhDJ # nnf5qebpeEq8KHZY4mzZt5yeiy8GTei7Jn+pXv8KzLhXvFZF0uwnJHuBhQG8TiqM # MiLd5VikZaOPZDPoWMo/cwpjQHU/J+q2z0ufYeKlnTky5ya6E/vQ/u7WCX9lEWBh # RCb/jdVapNv3GpxBAZadtVNL1wF+A/69bn0GyMHFudmEtePWw0+2M6dZJYI6Fg0y # Z4ZY2s1j9jT6dpfVM7JYh3QU+R5OXRVCjiuATMnCyapgmknUWrDHG6ckeF1RL6Wj # 9uS9hRN4V5er+rEbSbgyG7SRyjIJdZ/ocXfkpaxZeSczcOEA86a3K0PLboM+63Wn # cEhTeRQgh4wtRUoN+g3vo9TkF4GiH9oG/nRRXDJwFwLiugSl46RvOG4wggd6MIIF # YqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v # dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0y # NjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZY # IZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+ # lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDP # s0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJ # KecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJ # T4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qc # D60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm # 7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/b # wBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKer # jt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHt # bcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70 # lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYD # VR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1 # AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA # FHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9j # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIw # MTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIw # MTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGD # MD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Rv # Y3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8A # cABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQEL # BQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFt # g/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/Wvj # PgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvt # aPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+Z # KJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x # 9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3bl # QCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8U # vmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGb # pT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNo # deav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uo # zKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZizCCGYcC # AQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYG # A1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAl6mQ+3R # X/H7RwAAAAACXjANBglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYB # BAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0B # CQQxIgQgAXKNE1pS56v8h3CBQzQTgE8KxtOzUWP9Dajecn7cJPgwQgYKKwYBBAGC # NwIBDDE0MDKgFIASAE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbTANBgkqhkiG9w0BAQEFAASCAQBbiMDXszbSR1epUSlpbtrHKrDD # jYNTVo32gN3xQfRc5IH7De5C6gCZUUmKbQG+DaeL/3lTjd19/aGTfgYEiDWfO0p0 # UymB+nBimnfqXLob7tNCjD2CtHll2WrhPqTFW4CCj3j82Zxcsyeo+gUHuFW27d61 # XToflt1TPd0Sw8D1xLo0EhrlsynyXoPnlwn/ilEublhQgZIvX8eB+Ml8Ww0X5MAK # ShlJUScrKCf0jL1uohEtA+qQevg+QKq5TODklrLCdWOMT7YzydbpqSMIkLVNau4n # Zf1NPGLZwAaGqQFgTxivt2vqRYHfFzcuDJvcctefRlaw+YA6JwbfQkqcOU9UoYIX # FTCCFxEGCisGAQQBgjcDAwExghcBMIIW/QYJKoZIhvcNAQcCoIIW7jCCFuoCAQMx # DzANBglghkgBZQMEAgEFADCCAVgGCyqGSIb3DQEJEAEEoIIBRwSCAUMwggE/AgEB # BgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEINenI/Wi1KwfJHolCx/XKwXE # dCFqUvxeJeBWAeaH/bW+AgZh/V29+h0YEjIwMjIwMjE4MDcyOTUwLjQ1WjAEgAIB # 9KCB2KSB1TCB0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO # BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEt # MCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYw # JAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoyQUQ0LTRCOTItRkEwMTElMCMGA1UEAxMc # TWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEWUwggcUMIIE/KADAgECAhMz # AAABhnjlGYn4JEvMAAEAAAGGMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMB4XDTIxMTAyODE5MjczOVoXDTIzMDEyNjE5MjczOVow # gdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsT # JE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMd # VGhhbGVzIFRTUyBFU046MkFENC00QjkyLUZBMDExJTAjBgNVBAMTHE1pY3Jvc29m # dCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK # AoICAQDAjcbZam/oHgiMB+uB8mmd0849g7Vh3z6+V+gjExbeB0INP7Mhtp+DXik6 # 7S3R6RRDHrSns9p0fg6Oeo0gTWrqV0f2e2PWAh2Xgerit0QdNnokV0TbgNJtWiqp # H5HgjDjDcY9t9zZDeR/LIXKP4M6GYJbD8VmJNVOVPht16PIBbqv8mfh+vfEuNu+E # hNq2vfpXLLOBDRjhavvcfeBRwuNi7SqIe60MNvr6n7IMEaYoXOc5bzBW3sP67ZUQ # mgTomUrQSlUtm6x1LOF5y5TAlfFva7KABleWxr98eXBb1ieUGowcn6Kb0e4rlfjH # z/kHl2S4ihfmVYaMUxsPYDou78+ZQHiErQIXkbVhpS0GswTvcMAqTKmTtISbcGUl # fBj8atWhdZhQYQfJ+uQuTCzRGgQymggSB5tk0qqNHKdEmBHh88IqsSHASJNMBzgN # cZyLgcc6brgRDWD9IMcwWogpVLGhRuQZt0o0oeGZqG4isDLjB72zutkmyS95lhmI # Oa0C0G3+BCiPFtnW870LXVK2GSuaSRMwtB/1wPOVUQF67oqYdfZLN7qCCd7cjhzL # /khQucdneszhmklzSzYqkYsdpWsRDLjH+YCfjJph+B4fcwQBaRWPL+pMOHpwMIX+ # DLPdNpAO28WcArvQuq1sS8E90Gl4Ib+GT2XSVpjPCLLIZj8eowIDAQABo4IBNjCC # ATIwHQYDVR0OBBYEFBm2o0UD72Z0S7+HfdSEcw3rCFwuMB8GA1UdIwQYMBaAFJ+n # FV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAl # MjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKG # UGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0 # JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAw # EwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggIBAMPuZ6Eljd9M # coLiGP7AFYHznji5omIwwgeeEr041MztWtpNjPHRT9NwsnqDHDW1HMzm67ySzAk2 # Uc2ntF52wCLC+JVBlX0AvwhtlEslPA16ELCT4FVxjaCHdkZmbHy5q09mtG57KGFN # MPY+8VUut/CHaWIMb90Q80gdMqPv0OURw8hag4JSnunQ5EzBD3mRVqJulfz2m+OE # +XYWbQIE7eldcmDRvJ2lDl0MNO/+pvT5ZgX+81URT8ygwRCqVRZa5cQJOrHpNrIm # 4snq5TsrlDJORD+XbgiEaMPN/kARk6sg1jORZXI19Q6kjGcqxZME3aKOln9O6fmq # uaj280gNPSWhuCe6Vp7Xs1oQ72iIQkkfW1Dfnd2G5GL4DTQ9HvzWJiXMXklTUOsR # 8TI3HwJaARGL3QsqxiCFkEIONDcOImN9Rkuo414esl9yaHPn9t+bz5oBpQ+lkV4/ # SDQiid3pc2ThiJhtY8Wih9zQvBypIAu24gDLPp/d35RplmynjVTiEIigaPqGgMi5 # Tzf1uj+Zn8CARLAbEhezSBlToD7aohR7rRB0D3r3BZLO5wo6KyeD0cJJksXV2pzd # BRrCvQLRTjXvzgqj29yQAbdqTBi5UZyzqEz9KoSGh72MfB7henzUKtMHWX34Qh26 # QJs/STLPHRZnO156IM3mt2KJBH2YEm6WMIIHcTCCBVmgAwIBAgITMwAAABXF52ue # AptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgz # MjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxO # dcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQ # GOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq # /XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVW # Te/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7 # mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De # +JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM # 9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEz # OUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2 # ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqv # UAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q # 4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcV # AgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXS # ZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcC # ARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRv # cnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1 # AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA # FNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8y # MDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAt # MDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8 # qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7p # Zmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2C # DPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BA # ljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJ # eBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1 # MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz # 138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1 # V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLB # gqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0l # lOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFx # BmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IEly # ZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT # TjoyQUQ0LTRCOTItRkEwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAAa7YNHNaQqWOZfJJfWSiscvh8yeggYMw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUF # AAIFAOW5p+wwIhgPMjAyMjAyMTgxMzA2NTJaGA8yMDIyMDIxOTEzMDY1MlowdDA6 # BgorBgEEAYRZCgQBMSwwKjAKAgUA5bmn7AIBADAHAgEAAgIIfTAHAgEAAgIRXzAK # AgUA5br5bAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBABRB/HhLaqL+ZTuC # atDtF6DnfvmQ3iju1yVBQDvcQIFvf2Dttgr/Yq0VIB1Q+BoRr4z2zsaUt/mFp9Ec # suIbNNf65OC4evXnk3xhQpok7ZcGzETNz+PedHnLjVL9PP6d4jp375fswNXczGyo # 8Bz5RVjuxlg8OUSFznwhi9vw6ek3MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAGGeOUZifgkS8wAAQAAAYYwDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQgfqC0ljdFePOwqh85rh/ZEtUK1ovlTpStz35ckwQpOYEwgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCAamYjgsiwIVMaJjJ9EBHubsVraC7FU0jDXuZwC # KrxCfjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # hnjlGYn4JEvMAAEAAAGGMCIEIGzdfAAI9HojUquYgz+XJ5UCJjoQEuLb0srRJJEC # Zsy1MA0GCSqGSIb3DQEBCwUABIICAGiRsbiD3clv0vpd5fTNTKaLpWm5gxI5JDn9 # ZIyzUF+Lu/wrZM6vbPp3F0JzC5J1bLxCVPWEUhP8djCIiIU+c1D/MctsOhcfxr59 # xyuc6zUQ7xraM98rIEliN8a3OrumDvSi4ZN7cBQYAav8X79LHLHS2XRFXr+Q3sPM # mxawn6qxjF1UTWN+dkusxKNr0pbvk8CxC5PSlNdIR8m2MCsmMEtLnmhtHglvQkob # L9U/zPQryXa4biD9TeAEY2preM7Vmd1d8TF9D1mUOqQaqGLYCJ+XY9XOnBshdGOc # +/zCAEeHqZBDJM2I6cNIcxTEiRmwcS7bg+pEerS3piTE5jZduOroImA4yW7W3/4D # BZKNo2HLm+Gx2mWIqPIg+5titUlPZdIVuD0OonFPmRTy8LiYx+JwdtW/o+Ma+mHM # +yHXcNt+FMYEShTy64huQ5KWMdQJMn44emzB3IApRLklJz1tyg5TblxW2UnRPqzU # h8GUNJfj3LD9GDJeUghzUtVbpq2EqRF1mLInADrA3I6aI57gFgF9luYC6tGbUZrR # Yu+84rYhXP7aNs+nuWgADNcf80+g+aCAEJzj3sd4LyCCTfmBbvU5yXXVTfZhcOhC # RUnfGppnUVGrcu1ftHtoOxnXM8Z6FMcM/wfJlR869vGI/ValC1vuddo7Tv5LSjFk # IUY5Dw34 # SIG # End signature block |