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

  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.

Disable AAD Connect Schedule

  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 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.
  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 Forest .   In 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=<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 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=<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 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.

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!