# 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)

# 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).

1. Log into the resource forest domain controller.  In my lab, this is gss-dc.gsshared.local.
2. Launch Active Directory Users and Computers.
3. Create an Organizational Unit called something easy to identify, such as Shared GAL.

4. Then, underneath it, create an OU for each organization that will be utilizing the shared resource forest.
and
5. 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.
6. Select View | Advanced Features.
7. 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.
8. 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.
9. 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.

1. Launch an elevated PowerShell window.
2. Run the following command to disable the synchronization scheduler:
Set-ADSyncScheduler -SyncCycleEnabled $false ## Create Connector 1. Click Start and select the Synchronization Service. 2. Click the Operations tab, and then select Create from the Actions Pane (or right-click | Create in the empty area). 3. Select the type of connector as Active Directory Domain Services. Enter a name and a description and click Next. 4. Enter the resource forest name, the admin account created previously for this account forest, password, and the DNS domain name. Click Next. 5. Select the domain partition shown, and then click the Containers button. 6. Deselect all containers except the Shared GAL container created previously. Click OK when finished. 7. Click Next. 8. On the Configure Provisioning Hierarchy page, click Next without making any changes. 9. On the Select Object Types page, click contact to add it to the list of selected object types. Click Next. 10. 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 11. 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. 1. On the Connections tab, right-click on the Shared GAL connector and click Configure Run Profiles. 2. Click New Profile. 3. Enter Full Import in the name field and click Next. 4. Select the Full Import step type and click Next. 5. Click Finish. 6. Click New Profile. 7. Enter Full Synchronization in the name field and click Next. 8. Select the Full Synchronization step type and click Next. 9. Click Finish. 10. Click New Profile. 11. Enter Delta Import in the name field and click Next. 12. Select the Delta Import (Stage Only) step type and click Next. 13. Click Finish. 14. Click New Profile. 15. Enter Delta Synchronization in the name field and click Next. 16. Select the Delta Synchronization step type and click Next. 17. Click Finish. 18. Click New Profile. 19. Enter Export in the name field and click Next. 20. Select the Export step type and click Next. 21. Click Finish. You should now have 5 run profiles configured. 22. 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. 1. From inside the Synchronization Service Manager, click Metaverse Designer. 2. Click the person object type. 3. Click Add Attribute. 4. Click New Attribute. 5. Enter a new attribute name. In this example, I’m going to use customMailNicknameBe exactly sure of what you enter. This is case-sensitive, and bad things will happen if you capitalize it differently throughout the configuration process. 6. Click OK to close the Add Attribute to Object Type dialog box. 7. Click the group object type. 8. Click Add Attribute. 9. 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: 1. Download it to each of the AAD connect servers participating in the synchronization. 2. Launch an elevated PowerShell window and change to the directory where you’ve saved the script. 3. 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" 4. Select the AD connector that represents your current Active Directory Account ForestIn this case, I’m going to choose 1. 5. Select the AD connector that represents the Active Directory Resource Forest. In this case, I’m going to choose 2. 6. 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. 1. Launch the Synchronization Rules Editor. 2. Select Inbound under direction, and then click Add New Rule. 3. 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) 1. Click Next. 2. On the Scoping Filter page, click Next. 3. On the Join Rules page, click Next. 4. On the Transformations page, click Add. 5. Enter the following values:  Flow Type Target Attribute Source Apply Once Merge Type Expression targetAddress AuthoritativeNull Update 1. 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. 1. Select Inbound under direction, and then click Add New Rule. 2. 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) 1. Click Next. 2. On the Scoping Filter page, click Add Group. 3. Click Add Clause. Enter the following values:  Attribute Operator Value mailNickname ISNOTNULL 1. Click Next. 2. On the Join Rules page, click 3. On the Transformations page, enter the following values:  Flow Type Target Attribute Source Apply Once Merge Type Expression customMailNickname %Forest.Netbios% & “.” & [mailNickname] Update 1. 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. 1. Select Inbound under direction, and then click Add New Rule. 2. 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) 1. Click Next. 2. On the Scoping Filter page, enter the following values:  Attribute Operator Value mailNickname ISNOTNULL 1. Click Next. 2. On the Join Rules page, click 3. On the Transformations page, enter the following values:  Flow Type Target Attribute Source Apply Once Merge Type Expression customMailNickname %Forest.Netbios% & “.” & [mailNickname] Update 1. 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. 1. Select Inbound under direction, and then click Add New Rule. 2. 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) 1. Click Next. 2. On the Scoping Filter page, enter the following values:  Attribute Operator Value dn NOTCONTAINS .group. mail NOTCONTAINS @[organization SMTP] 1. Click Next. 2. On the Join Rules page, click Add group. 3. Click Add clause. 4. Enter the following values, clicking Add clause to add a line for each join rule:  Source Attribute Target Attribute Case Sensitive mailNickname customMailNickname mail mail 1. Click Next. 2. 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 mail 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 1. 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). 1. Select Outbound under direction, and then click Add New Rule. 2. 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) 1. Click Next. 2. On the Scoping Filter page, enter the following values for each domain for which the agency hosts mail:  Attribute Operator Value customMailNickname ISNOTNULL mail ISNOTNULL 1. Click Next. 2. On the Join Rules page, enter the following values:  Source Attribute Target Attribute Case Sensitive mail mail 1. Click Next. 2. 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=, 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 mail 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 1. 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). 1. Select Outbound under direction, and then click Add New Rule. 2. 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) 1. Click Next. 2. On the Scoping Filter page, enter the following values for each domain for which the agency hosts mail:  Attribute Operator Value customMailNickname ISNOTNULL mail ISNOTNULL 1. Click Next. 2. On the Join Rules page, enter the following values:  Source Attribute Target Attribute Case Sensitive mail mail 1. Click Next. 2. 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=, 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 mail 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 1. Click Add. # Create Custom Sync Schedule 1. Disable the default AAD Connect synchronization schedule. 1. Launch an elevated PowerShell prompt. 2. Run Import-Module ADSync 3. Run Set-ADSyncScheduler -SyncCycleEnabled$False
2. 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.
3. Replace the value after -ConnectorName with the connector name as it is displayed in the AAD Connect Synchronization Service Manager. It is cAsE sENsItIvE.
4. The values for -RunProfileName must explicitly match one of the values specified in the run profile configuration for the connector. It is cAsE sENsItIvE.

# 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!

Helping companies conquer inferior technology since 1997. I spend my time developing and implementing technology solutions so people can spend less time with technology. Specialties: Active Directory and Exchange consulting and deployment, Virtualization, Disaster Recovery, Office 365, datacenter migration/consolidation, cheese.

1. Michael says:

Thanks for confirming that ADMT is still the best way to go about the migration of accounts from one forest to another.
The only outstanding issue now, is that the latest ADMT version does not appear to confirm support for Window 2016/2019 domain controllers.

2. Michael Fabunmi says:

Very interesting deep dive into the guts of the AAD Connect tool.
Is there an easy enough way to modify the script (or follow the instructions) to create AD accounts rather than AD contacts? The context is that this could be used to replace the need for ADMT during user account migrations between source and target AD forests.

1. I would rather use ADMT, as it preserves SIDHistory and group memberships, and to my knowledge, is still a supported mechanism. You could change the object type to flow out as a user, I suppose.

This site uses Akismet to reduce spam. Learn how your comment data is processed.