AAD Connect, a dedicated resource forest, a custom connector, and a bunch of transform rules: a GalSync story (Part 2)
In part 1 of our adventure, we built an Azure AD lab to support configuring AAD Connect to work as a GalSync engine. In this post, we'll finish up the configuration. As a reminder, this is the what the overall solution will look like:
And, as I mentioned in part 1:
Please don't call Premier asking for support on this. They will hunt me down and give me a stern talking to. As any custom solution is, this is also unsupported. While it does work and only utilizes built-in connectors, AAD Connect (like Office 365) is an evergreen product, so there is potential that the steps outlined here may someday cease to work or functionality may be deprecated. This works with the build version of AAD Connect current as of this writing: 1.1.882.0 (September 8, 2018)
[toc]
Our story thus far
We definitely got a lot done in the last post:
- Starting 2 Office 365 Enterprise Trials, each representing a separate organization
- Setting up 3 virtual networks and network security groups
- Deploying 3 virtual machines into the network security groups
- Configuring each of those virtual machines as a new forest
- Two of the forests will be account forests, representing our two separate organizations
- One of the forests will be a resource forest, which will act as a staging location for shared global address list
- Provisioning 5,000 unique users accounts in each of the account forests
- Running the AAD Connect Network Testing Tool to verify that our two account forests can communicate with Office 365
- Running the AAD Connect installation in Express mode to configure our account forests to sync to their respective Office 365 tenants
Now that we've got the foundation laid, we're going to start configuring our environments to talk to each other and hopefully end up with 5,000 new contacts in each tenant organization.
Create Dns Conditional Forwarding Zones
As I stated in the original solution description, we're going to leverage the default Active Directory connectors. The AD connector requires AD DNS SRV record lookups to be successful, so in order to make that happen, we're going to create some conditional forwarding zones. We need to be able to resolve the shared or resource forest from both of the account forests. To achieve this, we will use PowerShell. As a reminder, our network configuration:
GalSyncTenantA, IP Range 10.0.0.0/24, DC IP: 10.0.0.4, NAT IP: 137.117.58.26
C:\>Get-ADForest
ApplicationPartitions : {DC=ForestDnsZones,DC=gstenanta,DC=local, DC=DomainDnsZones,DC=gstenanta,DC=local}
CrossForestReferences : {}
DomainNamingMaster : GSTA-DC.gstenanta.local
Domains : {gstenanta.local}
ForestMode : Windows2012R2Forest
GlobalCatalogs : {GSTA-DC.gstenanta.local}
Name : gstenanta.local
PartitionsContainer : CN=Partitions,CN=Configuration,DC=gstenanta,DC=local
RootDomain : gstenanta.local
SchemaMaster : GSTA-DC.gstenanta.local
Sites : {Default-First-Site-Name}
SPNSuffixes : {}
UPNSuffixes : {galsynctenanta.onmicrosoft.com
GalSyncTenantB, IP Range 10.0.1.0/24, DC IP: 10.0.1.4, NAT IP: 168.62.181.187
C:\>Get-ADForest
ApplicationPartitions : {DC=ForestDnsZones,DC=gstenantb,DC=local, DC=DomainDnsZones,DC=gstenantb,DC=local}
CrossForestReferences : {}
DomainNamingMaster : GSTB-DC.gstenanta.local
Domains : {gstenantb.local}
ForestMode : Windows2012R2Forest
GlobalCatalogs : {GSTB-DC.gstenanta.local}
Name : gstenantb.local
PartitionsContainer : CN=Partitions,CN=Configuration,DC=gstenantb,DC=local
RootDomain : gstenantb.local
SchemaMaster : GSTB-DC.gstenanta.local
Sites : {Default-First-Site-Name}
SPNSuffixes : {}
UPNSuffixes : {galsynctenantb.onmicrosoft.com}
GalSyncShared, IP Range 10.0.2.0/24, DC IP: 10.0.2.4, NAT IP: 23.96.103.200
C:\>Get-ADForest
ApplicationPartitions : {DC=ForestDnsZones,DC=gsshared,DC=local, DC=DomainDnsZones,DC=gsshared,DC=local}
CrossForestReferences : {}
DomainNamingMaster : GSS-DC.gsshared.local
Domains : {gsshared.local}
ForestMode : Windows2012R2Forest
GlobalCatalogs : {GSS-DC.gsshared.local}
Name : gsshared.local
PartitionsContainer : CN=Partitions,CN=Configuration,DC=gsshared,DC=local
RootDomain : gsshared.local
SchemaMaster : GSS-DC.gsshared.local
Sites : {Default-First-Site-Name}
SPNSuffixes : {}
UPNSuffixes : {}
Since the diagram shows exporting to and importing from the GalSyncShared forest, we'll need to be able to locate that forest from each of the account forests. So, we can run this in each of the account forests:
$DnsServers = @('<IP addresses of DC in resource forest')
Add-DnsServerConditionalForwarderZone -MasterServers $DnsServers -Name <resource forest FQDN>
In my environment, it looks like this:
$DnsServers = @('10.0.2.4')
Add-DnsServerConditionalForwarderZone -MasterServers $DnsServers -Name gsshared.local
In the previous post, we configured some network security groups. Now, it's time to test them out! As the solution requires, we need to verify that we have network connectivity to our resource forest from our account forests. Grab the AAD Network Tool and run it from each of the account forest DCs (GTSA-DC and GTSB-DC, in my lab) with the following parameters:
.\AADConnect-CommunicationsTest.ps1 -DCs <FQDN of one or more DCs in remote forest> -ActiveDirectory -ForestFQDN <resource forest FQDN> -Dns -Network
So, in my lab, it looks like this:
.\AADConnect-CommunicationsTest.ps1 -DCs gss-dc.gshshared.local -ActiveDirectory -ForestFQDN gsshared.local -Dns -Network
This test verifies that all of the networking and name resolution prerequisites are met in order to be able to add another AD connector to AAD Connect. Run this in each account forest and attempt to communicate with the resource forest.
Prepare the Resource Forest
In this step, we're going to prepare the resource forest and delegated service accounts. Similar to a standard mutli-forest configuration, we're going to need to specify an account to use to connect with in the remote resource forest. We're also going to specify which organizational unit structure we want to scope our connector to (well, we need to create it first, technically).
- Log into the resource forest domain controller. In my lab, this is gss-dc.gsshared.local.
- Launch Active Directory Users and Computers.
- Create an Organizational Unit called something easy to identify, such as Shared GAL.
- Then, underneath it, create an OU for each organization that will be utilizing the shared resource forest.
and - In the users container (or any other container not in the Shared GAL path), create two new users--one for each tenant. I'm going to name my accounts pretty obvious names: admin-tenanta and admin-tenantb.
- Select View | Advanced Features.
- Right-click on OU=Tenant A,OU=Shared GAL, select Properties, and then select the Security tab. Click Add, add admin-tenanta, and then click the Full Control check box under the Allow column.
- Click Advanced, and then click the entry for admin-tenanta. Click Edit. Ensure This object and all descendant objects is selected in addition to Full Control.
- Click OK. Repeat the procedure for OU=Tenant B,OU=Shared GAL and admin-tenantb.
Create Connector for Resource Forest
Now that we have name resolution and network connectivity established as well as an OU structure in the resource forest, we're going to start the AAD Connect configuration. A brief overview:
- Stop AAD Connect Sync Cycle Schedule
- Establish a new connector
- Create Run Profiles
- Create metaverse attribute
These steps will establish the connectivity between AAD Connect and the resource forest and configure the run steps that will allow connector to execute later. These steps will be performed on each of the account forest AAD Connect servers.
Disable AAD Connect Schedule
Launch an elevated PowerShell window.
Run the following command to disable the synchronization scheduler:
Set-ADSyncScheduler -SyncCycleEnabled $false
Create Connector
- Click Start and select the Synchronization Service.
- Click the Operations tab, and then select Create from the Actions Pane (or right-click | Create in the empty area).
- Select the type of connector as Active Directory Domain Services. Enter a name and a description and click Next.
- Enter the resource forest name, the admin account created previously for this account forest, password, and the DNS domain name. Click Next.
- Select the domain partition shown, and then click the Containers button.
- Deselect all containers except the Shared GAL container created previously. Click OK when finished.
- Click Next.
- On the Configure Provisioning Hierarchy page, click Next without making any changes.
- On the Select Object Types page, click contact to add it to the list of selected object types. Click Next.
- On the Select Attributes page, click the Show All checkbox, and then select the following attributes:
c
cn
co
company
department
description
displayName
division
extensionAttribute1
extensionAttribute10
extensionAttribute11
extensionAttribute12
extensionAttribute13
extensionAttribute14
extensionAttribute15
extensionAttribute2
extensionAttribute3
extensionAttribute4
extensionAttribute5
extensionAttribute6
extensionAttribute7
extensionAttribute8
extensionAttribute9
facsimileTelephoneNumber
givenName
homePhone
info
initials
l
mail
mailNickname
middleName
mobile
msExchRecipientDisplayType
msExchRecipientTypeDetails
objectGUID
otherHomePhone
otherTelephone
pager
physicalDeliveryOfficeName
postalAddress
postalCode
postOfficeBox
proxyAddresses
sn
st
street
streetAddress
targetAddress
telephoneAssistant
telephoneNumber
title - Click OK to complete the creation of the connector.
Create Run Profiles
Run profiles are action definitions for the connector. For example, if AAD Connect calls a profile with the Full Import action, it will import all objects in scope in the connected directory.
- On the Connections tab, right-click on the Shared GAL connector and click Configure Run Profiles.
- Click New Profile.
- Enter Full Import in the name field and click Next.
- Select the Full Import step type and click Next.
- Click Finish.
- Click New Profile.
- Enter Full Synchronization in the name field and click Next.
- Select the Full Synchronization step type and click Next.
- Click Finish.
- Click New Profile.
- Enter Delta Import in the name field and click Next.
- Select the Delta Import (Stage Only) step type and click Next.
- Click Finish.
- Click New Profile.
- Enter Delta Synchronization in the name field and click Next.
- Select the Delta Synchronization step type and click Next.
- Click Finish.
- Click New Profile.
- Enter Export in the name field and click Next.
- Select the Export step type and click Next.
- Click Finish. You should now have 5 run profiles configured.
- Click OK.
Create metaverse attribute
For this custom configuration, we're going to create a custom metaverse attribute to hold a unique value that we can assign to objects in the remote forest. In the event that we have two objects with otherwise identical properties (for example, two users name John Smith), we can use this stored value which is unique to this installation to ensure uniqueness of objects going to the resource forest.
- From inside the Synchronization Service Manager, click Metaverse Designer.
- Click the person object type.
- Click Add Attribute.
- Click New Attribute.
- Enter a new attribute name. In this example, I'm going to use customMailNickname. Be exactly sure of what you enter. This is case-sensitive, and bad things will happen if you capitalize it differently throughout the configuration process.
- Click OK to close the Add Attribute to Object Type dialog box.
- Click the group object type.
- Click Add Attribute.
- Select customMailNickname from the list and click OK.
Create Synchronization Rules
The synchronization rules is where all of the magic happens. You can download this script, which is all of the rules assembled here. If you have used a different custom attribute in the Metaverse, you'll need to specify it with -CustomMetaverseAttribute. To run the script:
Download it to each of the AAD connect servers participating in the synchronization.
Launch an elevated PowerShell window and change to the directory where you've saved the script.
The script requires a TargetOU parameter, so, you'll need to specify the OU that you created above for the forest that you're syncing from. For example, if we're configuring this in GalSync Tenant A (GSTA) , I'd use "OU=Tenant A,OU=Shared GAL,DC=gsshared,DC=local" as my OU path.
.\CustomGAL -TargetOU "OU=Tenant A,OU=Shared GAL,DC=gsshared,DC=local"
Select the AD connector that represents your current Active Directory Account Forest . In this case, I'm going to choose 1.
Select the AD connector that represents the Active Directory Resource Forest. In this case, I'm going to choose 2.
Confirm your choice. The script will create the necessary connectors.
Or, if you're a glutton for punishment, you can go through the process outlined here to create the sync rules manually.
In from AD - Prevent Contact Target Address
The purpose of this rule is to prevent the flowing of an AD user’s targetAddress into their corresponding contact’s targetAddress when the object gets synchronized out to the GAL.
- Launch the Synchronization Rules Editor.
- Select Inbound under direction, and then click Add New Rule.
- On the Description page, enter the following values:
Name | In from AD - Prevent Contact Target Address |
Connected System | Organization Active Directory connector |
Connected System Object Type | user |
Metaverse Object Type | person |
Link Type | join |
Precedence | 90 (or other unused value about 10 below default rules) |
- Click Next.
- On the Scoping Filter page, click Next.
- On the Join Rules page, click Next.
- On the Transformations page, click Add.
- Enter the following values:
Flow Type | Target Attribute | Source | Apply Once | Merge Type |
Expression | targetAddress | AuthoritativeNull | Update |
- Click Add.
In from AD - Flow CustomMailNickname - Group
The purpose of this rule is to populate the CustomMailNickname attribute on the objects that will be going to the Shared GAL. It will be used to help construct unique names in the event that multiple source objects have the same alias value.
- Select Inbound under direction, and then click Add New Rule.
- On the Description page, enter the following values:
Name | In from AD - Flow CustomMailNickname - Group |
Connected System | Organization Active Directory connector |
Connected System Object Type | group |
Metaverse Object Type | group |
Link Type | join |
Precedence | 98 (or other unused value higher than Prevent Contact Target Adress) |
- Click Next.
- On the Scoping Filter page, click Add Group.
- Click Add Clause.
Enter the following values:
Attribute | Operator | Value |
mailNickname | ISNOTNULL |
- Click Next.
- On the Join Rules page, click
- On the Transformations page, enter the following values:
Flow Type | Target Attribute | Source | Apply Once | Merge Type |
Expression | customMailNickname | %Forest.Netbios% & "." & [mailNickname] | Update |
- Click Add.
In from AD - Flow CustomMailNickname - User
The purpose of this rule is to populate the CustomMailNickname attribute on the objects that will be going to the Shared GAL. It will be used to help construct unique names in the event that multiple source objects have the same alias value.
- Select Inbound under direction, and then click Add New Rule.
- On the Description page, enter the following values:
Name | In from AD - Flow CustomMailNickname - User |
Connected System | Organization Active Directory connector |
Connected System Object Type | user |
Metaverse Object Type | person |
Link Type | join |
Precedence | 99 (or other unused value higher than Flow CustomMailNickname - Group) |
- Click Next.
- On the Scoping Filter page, enter the following values:
Attribute | Operator | Value |
mailNickname | ISNOTNULL |
- Click Next.
- On the Join Rules page, click
- On the Transformations page, enter the following values:
Flow Type | Target Attribute | Source | Apply Once | Merge Type |
Expression | customMailNickname | %Forest.Netbios% & "." & [mailNickname] | Update |
- Click Add.
In from AD - Shared GAL Contact
The purpose of this rule is to import objects from the Shared GAL to the AAD Connect metaverse.
- Select Inbound under direction, and then click Add New Rule.
- On the Description page, enter the following values:
Name | In from AD - Shared GAL Contact |
Connected System | Shared GAL (resource forest) |
Connected System Object Type | contact |
Metaverse Object Type | person |
Link Type | provision |
Precedence | 201 (or other unused value higher than all default values) |
- Click Next.
- On the Scoping Filter page, enter the following values:
Attribute | Operator | Value |
dn | NOTCONTAINS | .group. |
NOTCONTAINS | @[organization SMTP] |
- Click Next.
- On the Join Rules page, click Add group.
- Click Add clause.
- Enter the following values, clicking Add clause to add a line for each join rule:
Source Attribute | Target Attribute | Case Sensitive |
mailNickname | customMailNickname | |
- Click Next.
- On the Transformations page, enter the following values:
Flow Type | Target Attribute | Source | Apply Once | Merge Type |
Expression | c | Trim([c]) | Update | |
Direct | cn | cn | Update | |
Expression | co | Trim([co]) | Update | |
Expression | company | Trim([company]) | Update | |
Direct | countryCode | countryCode | Update | |
Expression | department | Trim([department]) | Update | |
Expression | description | IIF(IsNullOrEmpty([description]),NULL,Left(Trim(Item([description],1)),448)) | Update | |
Expression | displayName | IIF(IsNullOrEmpty([displayName]),[cn],[displayName]) | Update | |
Expression | extensionAttribute1 | Trim([extensionAttribute1]) | Update | |
Expression | extensionAttribute2 | Trim([extensionAttribute2]) | Update | |
Expression | extensionAttribute3 | Trim([extensionAttribute3]) | Update | |
Expression | extensionAttribute4 | Trim([extensionAttribute4]) | Update | |
Expression | extensionAttribute5 | Trim([extensionAttribute5]) | Update | |
Expression | extensionAttribute6 | Trim([extensionAttribute6]) | Update | |
Expression | extensionAttribute7 | Trim([extensionAttribute7]) | Update | |
Expression | extensionAttribute8 | Trim([extensionAttribute8]) | Update | |
Expression | extensionAttribute9 | Trim([extensionAttribute9]) | Update | |
Expression | extensionAttribute10 | Trim([extensionAttribute10]) | Update | |
Expression | extensionAttribute11 | Trim([extensionAttribute11]) | Update | |
Expression | extensionAttribute12 | Trim([extensionAttribute12]) | Update | |
Expression | extensionAttribute13 | Trim([extensionAttribute13]) | Update | |
Expression | extensionAttribute14 | Trim([extensionAttribute14]) | Update | |
Expression | extensionAttribute15 | Trim([extensionAttribute15]) | Update | |
Expression | facsimileTelephoneNumber | Trim([facsimileTelephoneNumber]) | Update | |
Expression | givenName | Trim([givenName]) | Update | |
Expression | homePhone | Trim([homePhone]) | Update | |
Expression | info | Left(Trim([info]),448) | Update | |
Expression | initials | Trim([initials]) | Update | |
Expression | ipPhone | Trim([ipPhone]) | Update | |
Expression | l | Trim([l]) | Update | |
Expression | Trim([mail]) | Update | ||
Expression | mailNickname | IIF(IsPresent([mailNickname]), [mailNickname], [cn]) | Update | |
Expression | middleName | Trim([middleName]) | Update | |
Expression | mobile | Trim([mobile]) | Update | |
Direct | msExchRecipientDisplayType | msExchRecipientDisplayType | Update | |
Direct | msExchRecipientTypeDetails | msExchRecipientTypeDetails | Update | |
Expression | otherFacsimileTelephoneNumber | Trim([otherFacsimileTelephoneNumber]) | Update | |
Expression | otherHomePhone | Trim([otherHomePhone]) | Update | |
Expression | otherIpPhone | Trim([otherIpPhone]) | Update | |
Expression | otherMobile | Trim([otherMobile]) | Update | |
Expression | otherPager | Trim([otherPager]) | Update | |
Expression | otherTelephone | Trim([otherTelephone]) | Update | |
Expression | pager | Trim([pager]) | Update | |
Expression | physicalDeliveryOfficeName | Trim([physicalDeliveryOfficeName]) | Update | |
Expression | postalCode | Trim([postalCode]) | Update | |
Expression | postOfficeBox | IIF(IsNullOrEmpty([postOfficeBox]),NULL,Left(Trim(Item([postOfficeBox],1)),448)) | Update | |
Expression | proxyAddresses | RemoveDuplicates(Trim(ImportedValue("proxyAddresses"))) | Update | |
Expression | sn | Trim([sn]) | Update | |
Expression | sourceAnchor | ConvertToBase64([objectGUID]) | Update | |
Direct | sourceAnchorBinary | objectGUID | Update | |
Constant | sourceObjectType | Contact | Update | |
Expression | st | Trim([st]) | Update | |
Expression | streetAddress | Trim([streetAddress]) | Update | |
Direct | targetAddress | targetAddress | Update | |
Expression | telephoneAssistant | Trim([telephoneAssistant]) | Update | |
Expression | telephoeNumber | Trim([telephoneNumber]) | Update | |
Expression | title | Trim([title]) | Update | |
Expression | cloudFiltered | IIF(IsPresent([isCriticalSystemObject]) || ( (InStr([displayName], "(MSOL)") > 0) && (CBool([msExchHideFromAddressLists]))) || (Left([mailNickname], 4) = "CAS_" && (InStr([mailNickname], "}") > 0)) || CBool(InStr(DNComponent(CRef([dn]),1),"\\0ACNF:")>0), True, NULL) | Update | |
Expression | mailEnabled | IIF(( (IsPresent([proxyAddresses]) = True) && (Contains([proxyAddresses], "SMTP:") > 0) && (InStr(Item([proxyAddresses], Contains([proxyAddresses], "SMTP:")), "@") > 0)) || (IsPresent([mail]) = True && (InStr([mail], "@") > 0)), True, False) | Update |
- Click Add.
Out to AD - Shared GAL User Contact
The purpose of this rule is to provision a new user contact object in the organization’s Office 365 GAL OU (the resource forest).
- Select Outbound under direction, and then click Add New Rule.
- On the Description page, enter the following values:
Name | Out to AD – Shared GAL User Contact |
Connected System | Shared GAL [resource forest] |
Connected System Object Type | contact |
Metaverse Object Type | person |
Link Type | provision |
Precedence | 300 (or other unused value higher than all other rules) |
- Click Next.
- On the Scoping Filter page, enter the following values for each domain for which the agency hosts mail:
Attribute | Operator | Value |
customMailNickname | ISNOTNULL | |
ISNOTNULL |
- Click Next.
- On the Join Rules page, enter the following values:
Source Attribute | Target Attribute | Case Sensitive |
- Click Next.
- On the Transformations page, enter the following values:
Flow Type | Target Attribute | Source | Apply Once | Merge Type |
Expression | c | Trim([c]) | Update | |
Expression | co | Trim([co]) | Update | |
Expression | company | Trim([company]) | Update | |
Direct | countryCode | countryCode | Update | |
Expression | department | Trim([department]) | Update | |
Expression | description | IIF(IsNullOrEmpty([description]),NULL,Left(Trim(Item([description],1)),448)) | Update | |
Expression | displayName | IIF(IsNullOrEmpty([displayName]),[cn],[displayName]) | Update | |
Expression | dn | "CN=" & [customMailNickname] & ",OU=<dept>, OU=Shared GAL,DC=[resource forest],DC=[tld]" | Update | |
Expression | extensionAttribute1 | Trim([extensionAttribute1]) | Update | |
Expression | extensionAttribute2 | Trim([extensionAttribute2]) | Update | |
Expression | extensionAttribute3 | Trim([extensionAttribute3]) | Update | |
Expression | extensionAttribute4 | Trim([extensionAttribute4]) | Update | |
Expression | extensionAttribute5 | Trim([extensionAttribute5]) | Update | |
Expression | extensionAttribute6 | Trim([extensionAttribute6]) | Update | |
Expression | extensionAttribute7 | Trim([extensionAttribute7]) | Update | |
Expression | extensionAttribute8 | Trim([extensionAttribute8]) | Update | |
Expression | extensionAttribute9 | Trim([extensionAttribute9]) | Update | |
Expression | extensionAttribute10 | Trim([extensionAttribute10]) | Update | |
Expression | extensionAttribute11 | Trim([extensionAttribute11]) | Update | |
Expression | extensionAttribute12 | Trim([extensionAttribute12]) | Update | |
Expression | extensionAttribute13 | Trim([extensionAttribute13]) | Update | |
Expression | extensionAttribute14 | Trim([extensionAttribute14]) | Update | |
Expression | extensionAttribute15 | Trim([extensionAttribute15]) | Update | |
Expression | facsimileTelephoneNumber | Trim([facsimileTelephoneNumber]) | Update | |
Expression | givenName | Trim([givenName]) | Update | |
Expression | homePhone | Trim([homePhone]) | Update | |
Expression | info | Left(Trim([info]),448) | Update | |
Expression | initials | Trim([initials]) | Update | |
Expression | ipPhone | Trim([ipPhone]) | Update | |
Expression | l | Trim([l]) | Update | |
Expression | Trim([mail]) | Update | ||
Expression | mailNickname | IIF(IsPresent([mailNickname]), [mailNickname], [cn]) | Update | |
Expression | middleName | Trim([middleName]) | Update | |
Expression | mobile | Trim([mobile]) | Update | |
Constant | msExchRecipientDisplayType | 6 | Update | |
Constant | msExchRecipientTypeDetails | 128 | Update | |
Expression | otherFacsimileTelephoneNumber | Trim([otherFacsimileTelephoneNumber]) | Update | |
Expression | otherHomePhone | Trim([otherHomePhone]) | Update | |
Expression | otherIpPhone | Trim([otherIpPhone]) | Update | |
Expression | otherMobile | Trim([otherMobile]) | Update | |
Expression | otherPager | Trim([otherPager]) | Update | |
Expression | otherTelephone | Trim([otherTelephone]) | Update | |
Expression | pager | Trim([pager]) | Update | |
Expression | physicalDeliveryOfficeName | Trim([physicalDeliveryOfficeName]) | Update | |
Expression | postalCode | Trim([postalCode]) | Update | |
Expression | postOfficeBox | IIF(IsNullOrEmpty([postOfficeBox]),NULL,Left(Trim(Item([postOfficeBox],1)),448)) | Update | |
Expression | proxyAddresses | RemoveDuplicates(Trim(ImportedValue("proxyAddresses"))) | Update | |
Expression | sn | Trim([sn]) | Update | |
Expression | sourceAnchor | ConvertToBase64([objectGUID]) | Update | |
Direct | sourceAnchorBinary | objectGUID | Update | |
Constant | sourceObjectType | Contact | Update | |
Expression | st | Trim([st]) | Update | |
Expression | streetAddress | Trim([streetAddress]) | Update | |
Expression | targetAddress | "SMTP:" & [mail] | Update | |
Expression | telephoneAssistant | Trim([telephoneAssistant]) | Update | |
Expression | telephoeNumber | Trim([telephoneNumber]) | Update | |
Expression | title | Trim([title]) | Update |
- Click Add.
Out to AD - Shared GAL Group Contact
The purpose of this rule is to provision a new group contact object in the organization’s Office 365 GAL (resource forest).
- Select Outbound under direction, and then click Add New Rule.
- On the Description page, enter the following values:
Name | Out to AD - Shared GAL Group Contact |
Connected System | Shared GAL (resource forest) |
Connected System Object Type | contact |
Metaverse Object Type | group |
Link Type | provision |
Precedence | 301 (or other unused value higher than Out to AD – Shared GAL User Contact rule) |
- Click Next.
- On the Scoping Filter page, enter the following values for each domain for which the agency hosts mail:
Attribute | Operator | Value |
customMailNickname | ISNOTNULL | |
ISNOTNULL |
- Click Next.
- On the Join Rules page, enter the following values:
Source Attribute | Target Attribute | Case Sensitive |
- Click Next.
- On the Transformations page, enter the following values:
Flow Type | Target Attribute | Source | Apply Once | Merge Type |
Expression | description | IIF(IsNullOrEmpty([description]),NULL,Left(Trim(Item([description],1)),448)) | Update | |
Expression | displayName | IIF(IsNullOrEmpty([displayName]),[cn],[displayName]) | Update | |
Expression | dn | "CN=.group." & [customMailNickname] & ",OU=<dept>, OU=Shared GAL,DC=[resourceforest],DC=[tld]" | Update | |
Expression | extensionAttribute1 | Trim([extensionAttribute1]) | Update | |
Expression | extensionAttribute2 | Trim([extensionAttribute2]) | Update | |
Expression | extensionAttribute3 | Trim([extensionAttribute3]) | Update | |
Expression | extensionAttribute4 | Trim([extensionAttribute4]) | Update | |
Expression | extensionAttribute5 | Trim([extensionAttribute5]) | Update | |
Expression | extensionAttribute6 | Trim([extensionAttribute6]) | Update | |
Expression | extensionAttribute7 | Trim([extensionAttribute7]) | Update | |
Expression | extensionAttribute8 | Trim([extensionAttribute8]) | Update | |
Expression | extensionAttribute9 | Trim([extensionAttribute9]) | Update | |
Expression | extensionAttribute10 | Trim([extensionAttribute10]) | Update | |
Expression | extensionAttribute11 | Trim([extensionAttribute11]) | Update | |
Expression | extensionAttribute12 | Trim([extensionAttribute12]) | Update | |
Expression | extensionAttribute13 | Trim([extensionAttribute13]) | Update | |
Expression | extensionAttribute14 | Trim([extensionAttribute14]) | Update | |
Expression | extensionAttribute15 | Trim([extensionAttribute15]) | Update | |
Expression | info | Left(Trim([info]),448) | Update | |
Expression | Trim([mail]) | Update | ||
Expression | mailNickname | IIF(IsPresent([mailNickname]), [mailNickname], [cn]) | Update | |
Constant | msExchRecipientDisplayType | 6 | Update | |
Constant | msExchRecipientTypeDetails | 128 | Update | |
Expression | proxyAddresses | RemoveDuplicates(Trim(ImportedValue("proxyAddresses"))) | Update | |
Expression | targetAddress | "SMTP:" & [mail] | Update |
- Click Add.
Create Custom Sync Schedule
- Disable the default AAD Connect synchronization schedule.
- Launch an elevated PowerShell prompt.
- Run Import-Module ADSync
- Run Set-ADSyncScheduler -SyncCycleEnabled $False
- Create new scheduled task to call each of the required run profiles for AD, AAD, and Shared GAL connectors. The scheduled task should be configured to execute every 30 minutes using an account that is a member of both the AADSync Admins group and the local Administrators group.
- Replace the value after -ConnectorName with the connector name as it is displayed in the AAD Connect Synchronization Service Manager. It is cAsE sENsItIvE.
- The values for -RunProfileName must explicitly match one of the values specified in the run profile configuration for the connector. It is cAsE sENsItIvE.
Sample Scheduled Task Script
Import-Module ADSyncInvoke-ADSyncRunProfile -ConnectorName "activedirectory.com" -RunProfileName "Delta Import"Invoke-ADSyncRunProfile -ConnectorName "tenant.onmicrosoft.com - AAD" -RunProfileName "Delta Import"Invoke-ADSyncRunProfile -ConnectorName "Shared GAL" -RunProfileName "Delta Import"Invoke-ADSyncRunProfile -ConnectorName "activedirectory.com" -RunProfileName "Delta Synchronization"Invoke-ADSyncRunProfile -ConnectorName "tenant.onmicrosoft.com - AAD" -RunProfileName "Delta Synchronization"Invoke-ADSyncRunProfile -ConnectorName "Shared GAL" -RunProfileName "Delta Synchronization"Invoke-ADSyncRunProfile -ConnectorName "tenant.onmicrosoft.com - AAD" -RunProfileName "Export"Invoke-ADSyncRunProfile -ConnectorName “activedirectory.com” -RunProfile “Export”Invoke-ADSyncRunProfile -ConnectorName "Shared GAL" -RunProfileName "Export" |
Run Custom Sync Schedule
If you ran the script at the top of this post, it would have created a custom sync schedule script for you. You can execute that, or, if you created your own custom sync schedule script, run that instead. You should be able to click on the Shared GAL connector to see the progress.
Verify
Now that we have objects running through the synchronization rules, we should be able to check a few places to make sure that objects are flowing.
First, check the the Shared GAL containers in the resource forest for contact objects.
Next, after a round of sync cycles, you should be able to check your tenants for objects from the partner organizations forest as contact objects.
That's all she wrote! Be fruitful and multiply contacts!