Exchange Online

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


Exchange Online – merge a soft deleted mailbox into an active mailbox

Hi! hope you are well… I used this script yesterday and thought “I must post about that!” So here it is… 😃

This can be a good way to deal with an employee leaving, or if you end up with a dual-mailbox scenario, although Microsoft have made that much less likely since adding the Hybrid Exchange option to AD Connect a while back…

Right, enough kafuffle – onward! 👀

Suppose a user leaves – your off-boarding procedure may include something like:

  • Exporting the mailbox to a PST file which is stored somewhere
  • Converting the mailbox to a Shared Mailbox and giving a another user access to it

But what if there is an incoming user that would benefit from having the existing data in their newly created mailbox? It might be a training coordinator or HR administrator’s mailbox that has a heap of relevant information that the incoming user can search through for information. This is much easier to use when it is in your main mailbox, rather than an attached shared mailbox (or worse a PST file!).

This script will connect to Exchange Online and prompt you with a list of soft-deleted mailboxes (these are the ones that have been deleted but remain for 30 days before being permanently deleted). Select the recently deleted mailbox and click OK. Now you are prompted with a list of all active mailboxes. Select the mailbox to merge the data into and click OK. Now the soft-deleted mailbox will be merged into the active mailbox. Superb!

Make sure you have run ‘Install-Module ExchangeOnlineManagement‘ so you can run the script successfully.

Here’s the code:

# set variables to null
$source = $null
$target = $null

#Connect to EOL...
Try {
    Connect-ExchangeOnline -ShowBanner:$false
    Catch {
        Write-Warning "Failed to connect to Exchange Online!"
        Read-Host "Press a key to exit..."

# prompt to select source mailbox
$source = Get-Mailbox -SoftDeletedMailbox -ResultSize unlimited | Select DisplayName,ExchangeGuid,PrimarySmtpAddress,ArchiveStatus,DistinguishedName | Out-GridView -Title "Select source soft-deleted mailbox and click OK..." -PassThru

if ($source -eq $null) {
        Write-Warning "No source selected. Press any key to exit..."

# prompt to select target mailbox
$target = Get-Mailbox -ResultSize unlimited | Select Name,PrimarySmtpAddress,DistinguishedName | Out-GridView -Title "Select target mailbox and click OK..." -PassThru

if ($target -eq $null) {
        Write-Warning "No target selected. Press any key to exit..."

# start merging source into target
Try {
    New-MailboxRestoreRequest -SourceMailbox $source.DistinguishedName -TargetMailbox $target.PrimarySmtpAddress -AllowLegacyDNMismatch
    Catch {
        $errormsg = $Error[0].Exception.Message
        Write-Warning "An error occured. $errormsg. Press any key to exit..."

As usual please let me know if you have any trouble or have a scenario that warrants a modified solution and I’ll try to help.

Until next time! 🍻✌😎✌🍻


Is your Hybrid Exchange Server SAFE or unknowingly EXPOSED?

Hello and happy 2022! 🥳 I hope the year has started off as well as it possibly can for you (aside from the holiday ending a bit too early of course!)… 🍺😭 …right back to work!

Now, is your Exchange environment safe!!?? Really? Is it?

I love this utility called ‘SMTP Diag Tool’. It’s great for seeing whether you can connect directly to port 25 of an Exchange server. In most cases, if you can, you can send unauthenticated email to accepted domains within the organisation, because that’s how email used to work right?

SMTP Diag Tool

Before we had a mail filtering service in front of Exchange, email was sent directly between servers (in fact if Exchange was configured to use SpamHaus and SpamCop lookups, it did a pretty good job itself!). But I digress…

In many cases lately I have found I can connect directly to, and send unchecked email to Exchange servers, most often using ‘mail’ or ‘webmail’ hostnames. This method bypasses MX records, mail filtering services and also tells you probably have port 443 wide open as well. Clients are shocked – bypassing my mail filter – really? Yep! Here are the scenarios:

  1. You have a mail filtering service, but nobody set up the firewall or Exchange rules to restrict inbound email to the services sending IP Addresses (applies to on-premises and 365).
  2. You have a Hybrid Exchange configuration, some or all mailboxes are in the cloud but you still have port 25 and 443 open to your on-premises server.
  3. And a less serious case but a definite gap to plug – you are cloud only or hybrid, but have not configured rules to stop other tenants from emailing you directly (this would need to be a targeted attack, but I’m sure the attackers will automate the creation of that attack in future if not already).

To fix this there are several options; the more configured the better!

  1. The best step to take is restricting incoming traffic at the firewall. If you can’t use ‘Internet databases’ as firewall objects, throw away your firewall and get a suitable FortiGate model.
  2. Use the ‘Microsoft-Azure’ and ‘Microsoft-Outlook’ objects to restrict incoming (and outbound if you can) 25 traffic to Microsoft’s servers (if you are hybrid and using Defender mail filtering) OR by restricting to your mail filtering service’s IP range for port 25 (if you are on-premises, or routing through it).
  3. Use the Exchange Hybrid Agent! Re-run the wizard and choose the Agent option (you may need to install it manually first if running 2010). This allows you to close port 443 inbound entirely, by performing free / busy lookups and mailbox moves through an Azure App Proxy.
  4. For port 443, you can also use an Azure Application Proxy to act as a gateway to your environment if you are not Hybrid and it must be contactable from the internet (IMO Everyone should be using this for internet facing services – then you can close all inbound ports on your firewall and let Microsoft do the work!! (security team = ✅)
  5. If you do have inbound 443, use IIS with the URL Rewrite module to block access to any virtual directories that are not required (you’re not still using ActiveSync I hope!!?).
  6. You can also modify the Default Receive Connector in Exchange only to accept from a filtering service, or 365, but this is harder to maintain and shouldn’t be needed if the firewall is configured correctly.
  7. For the cloud-only and Hybrid scenarios, make sure you have implemented the Exchange rules to ensure only mail using your MX record will be delivered, ensuring it traverses the Mail Filtering protection of Exchange Online: Advanced Office 365 Routing: Locking Down Exchange On-Premises when MX points to Office 365 – Microsoft Tech Community.

I also recommend performing the following tasks to ensure maximum security for your environment, first focusing on Hybrid Exchange seeing as that is the most common scenario.

  • AD Connect – if it’s been around for a while, make sure it is now on a 2016/2019 server with TLS 1.2 enabled. You must have these OS to install the latest version. Also make sure it is configured for Hybrid Exchange while you are there.
  • Hopefully you are on Exchange 2016 (since that is free for Hybrid and 2019 is not), if not plan and get it done ASAP. Don’t use 2 vCPU and 8GB memory for your Exchange Hybrid server, what are we, skinflints? 4 vCPU and 16GB are recommended and should be easily achievable in any environment. If not then I cry for you… 😭😭
  • Use Azure Automation with Server Update Management to automatically patch your on-premises servers, even if you don’t have Azure yet this is worth enabling it for… WSUS – Yuuuuk! 🤮🤮

Until next time – chur chur from Simon!


Remove proxy address for specific domain from Exchange Distribution groups

This script can be run after connecting to Exchange Online or on-premises environment. Replace “” with the suffix you want to remove 👍

Thanks to me mate Sailesh who loooves his porkchops!! 🤣🤣

# Remove proxy address for "" from Exchange Distribution groups

$domainname = ""

$groups = Get-DistributionGroup -Resultsize unlimited | where {$_.EmailAddresses -like "*$domainname*"} 

foreach ($group in $groups) {  

    $groupidentity = $group.identity
    $addresstoremove = $group.Alias+"@$domainname"

    Set-DistributionGroup $groupidentity -EmailAddresses @{remove=$addresstoremove}

# End