GAL separation with Address Book Policies

Just a quick one on ABPs – there are many posts about this topic now, but there are few that mention how to resolve issues such as recipients not appearing in the list when they should be. In this case you need to ‘tickle’ (yes that’s the official Microsoft term) the objects to get them to play ball.

ABPs seemed complex when I first looked at them, and my first introduction was with a tenant that had 20,000 user objects! We don’t get many opportunities to work with environments of this size in New Zealand, so it was a great job to get involved with. #dontmessitupmaaate!

ABPs are most commonly used in large environments though, or where separation is needed. Examples of this are:

  • Multiple schools under one tenant – you don’t want students from one school seeing students from other schools in the Global Address List.
  • Multiple companies under one tenant. You don’t want Fabrikam users seeing Contoso users.
  • In both of these scenarios, you may want management or executive level staff to see all recipients in their GAL.

Here is the code to create each ABP… I’m using my fictional company SB Enterprises which exists in a large multi-company tenant. Customattribute1 is used across the tenant to identify the objects related to a particular company with a three-letter-acronym. (this could be the Company attribute, or any other attribute as long as we can use it for filtering in the commands). For Meeting Room, Equipment, or Contact objects, those are created with the company TLA at the front e.g. SBE_MeetingRoom1.

Firstly let’s connect to Exchange Online powershell:

Connect-ExchangeOnline -UserPrincipalName insertadminupnhere

Now let’s create some address lists to include in our policy:

# this one contains all our recipients (users, groups, and shared mailboxes)
New-AddressList -Name "SBE_AddressList" -RecipientFilter "(CustomAttribute1 -eq 'SBE')"

# this one contains our groups
New-AddressList -Name "SBE_Groups" -RecipientFilter "(ObjectClass -like 'group') -and (CustomAttribute1 -eq 'SBE')"

# this one contains our shared mailboxes
New-AddressList -Name "SBE_Shared Mailboxes" -RecipientFilter "(RecipientTypeDetails -eq 'SharedMailbox') -and (CustomAttribute1 -eq 'SBE')"

# this one contains our rooms
New-AddressList -Name "SBE_Rooms" -RecipientFilter "(RecipientTypeDetails -eq 'RoomMailbox') -and (Name -like 'SBE_*')"

# this one contains our contacts
New-AddressList -Name "SBE_Contacts" -RecipientFilter "(RecipientType -eq 'MailContact') -and (Name -like 'SBE_*')"

Sweet as lemon pie! Now let’s get a list of all the objects that should be included in each list:

$filter = (Get-AddressList "SBE_AddressList").recipientfilter
Get-Recipient -ResultSize unlimited -RecipientPreviewFilter $filter | Out-GridView

$filter = (Get-AddressList "SBE_Groups").recipientfilter
Get-Recipient -ResultSize unlimited -RecipientPreviewFilter $filter | Out-GridView

$filter = (Get-AddressList "SBE_Shared Mailboxes").recipientfilter
Get-Recipient -ResultSize unlimited -RecipientPreviewFilter $filter | Out-GridView

$filter = (Get-AddressList "SBE_Rooms").recipientfilter
Get-Recipient -ResultSize unlimited -RecipientPreviewFilter $filter | Out-GridView

$filter = (Get-AddressList "SBE_Contacts").recipientfilter
Get-Recipient -ResultSize unlimited -RecipientPreviewFilter $filter | Out-GridView

Looking good? Now let’s create the Global Address List and the Offline Global Address List:

# the global address list has it's own filter for all SBE objects
New-GlobalAddressList -Name "SBE_GlobalAddressList" -RecipientFilter "(CustomAttribute1 -eq 'SBE') -or (Name -like 'SBE_*')"

# the offline address list includes the address lists we created
New-OfflineAddressBook -Name "SBE_OfflineAddressList" -AddressLists "SBE_AddressList","SBE_Groups","SBE_Shared Mailboxes","SBE_Rooms","SBE_Contacts"

Great! Now we can create the Address Book Policy using all of the above:

New-AddressBookPolicy -Name "SBE_ABP" -AddressLists "SBE_AddressList","SBE_Groups","SBE_Shared Mailboxes","SBE_Contacts" -OfflineAddressBook "\SBE_OfflineAddressList" -GlobalAddressList "\SBE_GlobalAddressList" -RoomList "\SBE_Rooms"

Done! Well almost – we have to assign it to the users… I recommend assigning to a pilot group first to get some feedback in case of any issues. When ready, use this command to assign the ABP to all applicable users:

$SBE_ABP = Get-Mailbox -ResultSize unlimited -Filter "(RecipientTypeDetails -eq 'UserMailbox') -and (CustomAttribute1 -eq 'SBE')"; $SBE_ABP | foreach {Set-Mailbox -Identity $_.Identity -AddressBookPolicy 'SBE_ABP'}

Now, there are several things that can make it seem like things are not working, but I can tell you 99% of the time you just have to wait. It’s the old ‘cloud time phenomenon’ where things may take from 1 to 48 hours to take effect 🤣🤣🤣.

The most common issue I have come across is someone in the pilot group pointing out that someone or something is missing from the GAL. This is due to the object not being processed when the Address List was created. It should be a member of the filter; and it is upon object creation or update that membership of address list filters is determined.

This is where ‘tickling’ comes in. You need to change something i.e. any attribute of the offending object, then change it back again.

You can do this in the portal for a single object (e.g. change the last name one letter, save, then change it back again)… but seeing as I know this problem exists, I now do this as part of the initial setup so I don’t have to deal with it later.

Let’s run this to change an attribute – I’ve checked all objects Customattribute5 is blank, so I can use it for this purpose (you don’t have to use tickle obviously, any value will do):

# get the users we want to 'tickle'
$users = Get-User -ResultSize unlimited -Filter "Customattribute1 -eq 'SBE'"

# tickle them by modifying a value (make sure the value was null for all objects beforehand)
foreach ($user in $users) {

    $id = $user.DistinguishedName
    Set-Mailbox $id -CustomAttribute5 "tickled"

# then return the value to null
foreach ($user in $users) {

    $id = $user.DistinguishedName
    Set-Mailbox $id -CustomAttribute5 $null

And voila, the object now appears on the list (taking into account the ‘cloud time phenomenon’ mentioned above).

As always, thanks for coming dudes & dudettes! ✌🍻✌


5 thoughts on “GAL separation with Address Book Policies”

  1. Excellent post, Simon. I’ve got a similar setup to your commands for the “Multiple schools under one tenant” reason. I’ve got a problem at the moment though, we have some staff who teach at multiple schools. I’ve been able to get a CustomAttribute1 = ‘ABC,DEF,GHI’ setting to work as my code splits sites at the comma.
    Unfortunately it leaves the user with the last Address Book Policy assigned to them as you can’t assign multiple address book policies. I don’t suppose you’ve come across that problem and come up with a workaround?
    At the moment I can only think of making bespoke ABPs for those roaming users on the fly during the script. I would loop through their sites and include all Room Lists, Address Lists, OABs, GALs that have a displayname matching ‘^$sitecode’ but that is going to get very messy – especially for anyone looking at the configuration later if I get hit by a bus 🙂
    Would love to hear any thoughts on this.

    1. Hi Kieran, and thanks! That is tricky – I’m guessing the staff may work at 2 out of 3 schools? If they worked at ‘all’ schools, or if it doesn’t matter that they see ‘everyone from all schools’, you could leave those staff using the default address book. That way they will see everyone in the default GAL, similar to the CEO example in this MS doc: Unfortunately, if you wanted them to see only 2 of 3 schools you would need an ABP for that unique scenario. As far as having them appear on multiple ABP GALs, I’d suggest having an ‘exception group’ in the filter used for each ABP. You can then add them to the exception groups as required so they appear on ABP GALs other than their ‘primary’ school (no pun intended!). Hope that helps and good luck!


  2. Hi, this is exactly what i’m looking for, thank you.
    Quick question if i may… what ps syntax did you use to apply the custom attribute to your groups?

    1. Hi! Glad it helped… in my cases AD was syncing to Azure AD, so I used AD PowerShell to set the attributes like below. You can use -Replace for single values, or for multiple-value field like ProxyAddresses use -Add or -Remove to preserve the existing data. Cheers, Simon.
      $csv= import-csv c:\groups.csv
      foreach ($group in $csv) {
      $distname = $group.distinguishedname
      Set-ADGroup $distname -Replace @{extensionAttribute1=”$office”;extensionAttribute14=”$attrib14″;extensionAttribute15=”$attrib15″}

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top