address book policy

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! ✌🍻✌