It is quite typical to have either a multiple-forest directory synchronisation solution, or a directory synchronisation solution, in an organisation that also performs some type of Global Address List (GAL) synchronisation, a.k.a. GALSYNC. Forefront Identity Manager (FIM) provides an out-of-box solution for GALSYNC that many Exchange Server customers utilise today. Introduce Office 365 into the mix and you’ll be implementing a solution that Microsoft call Directory Synchronization, or DIRSYNC.
DIRSYNC is commonly achieved using the Directory Synchronization appliance – a service that runs on-premises and “replicates” Active Directory Domain Services (AD DS) objects into the cloud, specifically into Windows Azure Active Directory (AAD). DIRSYNC is basically FIM, with some serious operational efficiency wrapped around it, however the DIRSYNC appliance doesn’t work in a multi-forest/multi-Exchange organisation topology, therefore the solution is handled by FIM (but a separate instance of FIM to that doing the GALSYNC as DirSync and GALSync cannot really exist on the same box).
Customers with multiple on-premises AD DS forests with two or more Exchange Server organisations will be synchronising the GALs between organisations. When it comes to synchronising these on-premises identities into AAD there are two typical configurations that I’m discussing in the context of this post:
- One side of the GALSYNC is moving to Office 365 (figure 1)
- All forests are moving to Office 365 (figure 2)
Figure 1: DIRSYNC and GALSYNC
Figure 2: Multi-forest DIRSYNC (FIM) and GALSYNC
When moving on-premises mailboxes to the cloud, a.k.a. Exchange Online (EXO), there exist a couple of problems:
- If an on-premises user emails a recipient in one of the external Exchange organisations using a contact in the local GAL and is migrated to Exchange Online (EXO) the Outlook Recipient cache can break if the GAL in EXO is built from the real on-premises objects and not the synchronised contact objects.
- The inverse is also true. If an EXO user is off-boarded, i.e. moved back to on-premises, the recipient cache won’t be able to locate the EXO object using the on-premises GAL.
Thankfully these things are more or less handled by the GALSYNC that ships with FIM and the DIRSYNC hybrid logic. If you have GALSYNC you can probably skip to the next section, where I get to crux of this post; if you don’t have GALSYNC, and instead implement your own scripted process, you might be interested what I have to say next.
GALSync and X500 proxyAddresses
The Outlook recipient cache issues are handled by adding X500 proxyAddresses values. The source object’s legacyExchangeDN must be added to its proxyAddresses attribute, as an X500 address. All downstream contact object’s legacyExchangeDN values must also be added to the proxyAddresses attribute of the source object and it’s downstream replicas. By doing this you ensure “replyability” when a mail object moves between Exchange organisations. The OOB GALSYNC solution does this for you. If you have a custom solution be sure to mimic this behaviour. The picture below depicts this – blue arrows show the general flow of information from source (user) into downstream (contact). Orange arrows are the downstream/target object values going back to source. In the end all objects have the same proxyAddresses values. I didn’t want to muddy the drawing with arrows from contact to contact, but the end result is that this does happen.
Figure 3: High-level depiction of GALSYNC
The off-boarding issue is fixed by enabling Hybrid mode in DirSync, a.k.a. write-back. In FIM that means implementing the required EAFs on each ADMA. This is actually needed in a multi-forest topology even if hybrid isn’t being used – you need to get the cloudLegacyExchangeDN into the on-premises proxyAddresses.
Hybrid write-back and GALSync
When you enable DirSync hybrid (or implement your own EAF in the FIM pattern) you will, unless you configure your EAF accordingly, write the cloudLegacyExchangeDN into the proxyAddresses of contact objects that are owned by GALSYNC, i.e. the authoritative objects are in another Exchange organisation. The next time GALSYNC runs it will remove the X500 address (the proxyAddresses attribute of the GALSYNC-managed contact is owned by GALSYNC). The next time DirSync runs it will add the value again. This process will repeat during each synchronisation and will be quite normal for each isolated instance of the FIM Synchronization service and won’t actually generate errors. It’ll just cause replication churn in the on-premises AD DS forest.
Arguably you can stop this in the FIM DIRSYNC service, however should you (you can’t stop this if you’re using the DirSync appliance)? This is actually needed. If EXO users are sending emails to these contacts and are off-boarded back to the on-premises infrastructure their Outlook recipient cache will result in an NDR. Or worse, it could work sometimes and other times not, depending on the timings of synchronisation and OAB downloads.
The solution to this issue is to tweak the GALSYNC code to not remove X500 addresses contributed by EXO. I’ll discuss how I have handled this in the next section. First, though, if you don’t care about how I’ve handled it and just want to download a DLL that works…
If you don’t want to modify the source code and compile it yourself you can download a customised DLL from here.
Couple of points about the DLL. I upgraded the default solution by loading it in Visual Studio 2012. I then changed the target framework to .NET 3.5 (runtime of .NET 2.0).
I’ll show you the original code and the changes required to fix the issue. It’s very simple.
The original code
Here’s the original EAFProxyAddressesForwards sub-routine (method) that updates the proxyAddresses attribute.
Private Sub EAFProxyAddressesForwards( _ ByVal csentry As CSEntry, _ ByRef mventry As MVEntry) ' ' flow to others ' Dim value As value If Not csentry.ObjectType.Equals(CONTACT) Then LogAndThrowUnexpectedDataException( _ "Unhandled object type in MapProxyAddressesForward " _ & "called with entry " & csentry.ToString _ & " and mventry " & mventry.ToString) End If ' ' We clear all the values first if the object is not in ' the authoritative container. ' If Not IsInContactOUs(csentry) Then csentry(PROXY_ADDRESSES).Delete() End If For Each value In mventry(PROXY_ADDRESSES).Values InsertProxyAddress(value.ToString, _ csentry(PROXY_ADDRESSES).Values) Next ' ' we take all the values from MV and add to cs. ' NOTE: we can not distinguish whether some MA outside GALSync ' contributes any values ' For Each value In mventry(LEGACY_EXCHANGE_DN).Values InsertProxyAddress(X500_PREFIX & value.ToString, _ csentry(PROXY_ADDRESSES).Values) Next End Sub
Basically, delete the CS value and flow the MV value. Note that this is between the metaverse (MV) and the connector space (CS), and doesn’t result in changes to the actual connected system unless there are new elements in a given synchronisation.
The modified code
Here’s my modified code. I’ve added some comments to show where I begin and end the customisations. I’ll explain the two customisations after you’ve read the code.
Private Sub EAFProxyAddressesForwards( _ ByVal csentry As CSEntry, _ ByRef mventry As MVEntry) ' ' flow to others ' Dim value As Value If Not CSEntry.ObjectType.Equals(CONTACT) Then LogAndThrowUnexpectedDataException( _ "Unhandled object type in MapProxyAddressesForward " _ & "called with entry " & CSEntry.ToString _ & " and mventry " & MVEntry.ToString) End If ' ***** BEGIN customisation ***** ' ' We clear all the values first if the object is not in ' the authoritative container. ' 'If Not IsInContactOUs(csentry) Then 'csentry(PROXY_ADDRESSES).Delete() 'End If Dim x500ProxyAddresses As New List(Of String) If Not IsInContactOUs(CSEntry) Then For Each value In CSEntry(PROXY_ADDRESSES).Values If value.ToString().Contains("/o=ExchangeLabs/") Then ' we need to preserve the O365 legacyExchangeDN value to write-back ' after clearing x500ProxyAddresses.Add(value.ToString()) End If Next CSEntry(PROXY_ADDRESSES).Delete() End If ' ***** END customisation ***** For Each value In MVEntry(PROXY_ADDRESSES).Values InsertProxyAddress(value.ToString, _ CSEntry(PROXY_ADDRESSES).Values) Next ' ' we take all the values from MV and add to cs. ' NOTE: we can not distinguish whether some MA outside GALSync ' contributes any values ' For Each value In MVEntry(LEGACY_EXCHANGE_DN).Values InsertProxyAddress(X500_PREFIX & value.ToString, _ CSEntry(PROXY_ADDRESSES).Values) Next ' ***** BEGIN customisation ***** ' add any preserved EXO proxyAddresses that might have come from the cloud Dim listItem As String For Each listItem In x500ProxyAddresses InsertProxyAddress(listItem, CSEntry(PROXY_ADDRESSES).Values) Next ' ***** END customisation ***** End Sub
There are two changes in the code above:
- Before deleting the CSEntry proxyAddresses value I check for any X500 addresses that contain the string /o=ExchangeLabs/. If I find one of these values I take a copy of it so that we can add it back shortly.
- After adding the metaverse values I then add the preserved X500 addresses that were added by DirSync and would have been removed by the original csentry(PROXY_ADDRESSES).Delete() command.
One little note, there’s obviously a using statement to handle the List<String> too:
If GALSYNC interacts with an Exchange organisation that is either migrating to Office 365 or going to be running as a hybrid cloud and on-premises messaging system you need to modify GALSYNC to allow Office 365 Directory Synchronisation to add an X500 proxyAddresses value to the on-premises, non-authoritative contact objects created, managed and deleted by the GALSYNC solution.
Hope this helps.