Share address book from one to other tenants in O365

Brass Contributor

Dear Adam.,


I want to share my address book between two tenants . I am confusing something about the term : address book and contacts.

Dear all friends and experts


As my understand:

In contact have all address book ? or in address have all contact ? or address book is same contacts


In my case. I want to share ( address book ) to other tenants, actually, I need share address book or contacts ?


Please give me some advise, and how to do that. Thank you so much.

Lee Nguyen

18 Replies

Thank you Admin,


So, actually, when people say : Share address book in O356 is share contact right ?


So this is guide for share contact ? and will I follow the the this one ?


Thank you so much,

Lee Nguyen,

A contact is usually an external user and the address book the list of people , addresses within your organization! The first link explains this.

Make sure to really understand the concept before sharing anything outside the org!!

Regards / adam

Dear Admin.,


Thank for your advises. 


From your link. I know that, I need share address book to external. But, O365 do not support share address book, to do that, we need have a tool from third-party.


How to do that, my customer, they want us provide a solution for that?, So can we share address-book in O365 at the moment.

Thank you so much.,

Lee Nguyen

May I ask why your customer demands you to share your internal address lists! This is not normal! It’s ok if you for example merge 2 companies and needs this! Then you setup trusts, common sync etc but not with a customer!

I would suggest them to create contacts in their tenant or use B2B:

best response confirmed by techno1795 (Brass Contributor)
With that said, feel free to vote on the uservoice for sharing GAL between tenants! Quite a big one!

Regards / Adam

Thank you Adam.,

In this case.


Can we create list of contact ( external ), end share to individual user in external ? , is this possible. ?


Thank you so much !

Lee Nguyen  

Dear Adam.,


Thank you for your advices.,

So if i follow as the guide to share contact its mean : End-user will share contact to external ? ( individual people),


Is there any solution for share contact from server side ?

Best and regards,

Lee Nguyen


@adam deltinger 


Do you have any doc's around how to do it for 2 companies that are merging? We are buying a company and I see how to setup shared free/busy but not contact list. 


We will eventually use bitTitan to migrate the users to 1 main tennant but for now I would like to shared GAL and Cal not just CAL..

I was going to do this manually with CSV files for the time being but if there is an easier way that would be great. 


Hi @CarlosSolrac did you ever get it to work for your 2 merging companies?

Do you have any documentation you could share?

Thank you




Hello Techno1795,

I was searching for a solution a long time.

Here the scenario:


- company holding with 3 child companies

- all 4 companies have their own tenant

- holding is AD domain synced to O365, only cloud mailboxes

- 1 child is plain O365

- 2 childs are hybrid Exchange organizations

- organizations with local ADs are bidirectional trusted


Goal is, that all organizations see the contacts in the other ones.


I finally used Azure Automation Powershell scripts for that. 


- I created multiple scripts that run once a day

- read the mailboxes from one Exchange organization or O365 tenant

- add a suffix to the display name like "(Organization One)" (for users belonging to multiple orgs.)

- use one attribute to identify the organization

- create a contact in each other organization, update if already exists

- run a second script or add a section to the first script that parses all contacts in the tenants and checks, if the mailbox still exists in the organization. If not, delete the contact. That cleans up your contacts. But be careful an check, if the connection to the organization is OK. Otherwise during connection problems, you'll lose all contacts till next run. ;)


Not nice, but it works.

@Bernd_SchneiderI have a similar issue that I'm dealing with and would appreciate if you could share a detailed insight on the script used and steps taken.

@Mirela_MM what I ended up doing was this:

I enabled the free/busy sharing via the documented MS way. I then created a contact for every user in the new company in the existing company and vice versa so it is a date and time solution, it solved my problem as we only needed about 30-45 days to move all the users.



I hope that helps.  

@Sunil50 Hi, that's the script.
It could be optimized, but it works:


# Publish O365 Users as Contacts in another tenant

# BSSE GmbH, Bernd Schneider


# Note:

# The text “ (Source-Tenant)” is appended to every contact to mark the contacts

# of the source company.

# Use at own risk!


# Connect Source-Tenant

Write-Output "Connect Source-Tenant"

$credObject = Get-AutomationPSCredential -Name "Source-Tenant-Admin"

Connect-MsolService -Credential $credObject


# Get Source-Tenant Users Excluding Guest Accounts

Write-Output "Read Source-Tenant MSOLUser"

$mySourceUsers=Get-MSOLUser -all|Where-Object {($_.UserPrincipalName -LIKE "*@Source-Tenant.*") -AND (-NOT ($_.UserPrincipalName -LIKE ("*#EXT#*")))  }


Get-PSSession | Remove-PSSession

# Create AzureRunAsConnection

$connectionName = "AzureRunAsConnection"



    # Get the connection "AzureRunAsConnection "

    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName   


    Write-Output "Logging in to Azure..."

    Add-AzureRmAccount `

        -ServicePrincipal `

        -TenantId $servicePrincipalConnection.TenantId `

        -ApplicationId $servicePrincipalConnection.ApplicationId `

        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 




    if (!$servicePrincipalConnection)


        $ErrorMessage = "Connection $connectionName not found."

        throw $ErrorMessage

    } else{

        Write-Error -Message $_.Exception

        throw $_.Exception




# Connect Target-Tenant

Write-Output "Connect Target-Tenant"

$credObject = Get-AutomationPSCredential -Name "Target-Tenant-Admin"

Connect-MsolService -Credential $credObject


# Function: Update-ExchangeOnlineContact 

function Update-ExchangeOnlineContact {

    param (



        $NameAndOrg=$User.DisplayName+" (Source-Tenant)"

        Set-Mailcontact $User.UserPrincipalName -Name $NameAndOrg -DisplayName $NameAndOrg -FirstName $User.FirstName -LastName $User.Lastname `

        -CustomAttribute9 "BSSE Office 365 Synchronisation"



# Function: Update-ExchangeOnlineContactEmail 

function Update-ExchangeOnlineContactEmail {

    param (



        $NameAndOrg=$User.DisplayName+" (Source-Tenant)"

        Get-MailContact -resultsize 99999|where-object {$User.DisplayName -eq $NameAndOrg}| ´

            Set-Mailcontact -ExternalEmailAddress $User.UserPrincipalName -CustomAttribute9 "BSSE Office 365 Synchronisation"



# Function: New-ExchangeOnlineContact 

function New-ExchangeOnlineContact {

    param (



        $NameAndOrg=$User.DisplayName+" (Source-Tenant)"

        New-Mailcontact -Name $NameAndOrg -DisplayName $NameAndOrg -FirstName $User.FirstName -LastName $User.LastName `

        -ExternalEmailAddress $User.UserPrincipalName 

        Set-Mailcontact -Identity $User.UserPrincipalName -CustomAttribute9 "BSSE Office 365 Synchronisation"



# Function: Connect to Exchange Online 

function Connect-ExchangeOnline {

    param (



        Write-Output "Connecting to Exchange Online"

        Get-PSSession | Remove-PSSession       

        $Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri -Credential $Creds -Authentication Basic -AllowRedi...

        $Commands = @("Get-Recipient","New-Mailcontact","Set-Mailcontact","Set-Mailbox","Get-Mailbox","Get-Contact","Get-MailContact", "Remove-Mailcontact", "Get-MailUser")

        Import-PSSession -Session $Session -DisableNameChecking:$true -AllowClobber:$true -CommandName $Commands | Out-Null



# Connect to Exchange Online

Write-Output "Connect Target-Tenant Exchange"

Connect-ExchangeOnline -Creds $credObject

# Load Data in Buffer

$SourceDisplay=Get-MailContact -resultsize 99999|where-object {$_.DisplayName -like "*(Source-Tenant)"}

$SourceContacts=Get-MailContact -resultsize 99999|where-object {$_.PrimarySMTPAddress -like "*Source-Tenant.*"}

$SourceExternal=Get-MailUser -resultsize 99999|where-object {$_.PrimarySMTPAddress -like "*Source-Tenant.*" -and $_.RecipientTypeDetails -eq "GuestMailUser"}


# Check if new Account using Email address

ForEach($User in $MySourceUsers) {

    $TempContact = $SourceContacts|where {$SourceContacts.PrimarySMTPAddress -eq ($User.UserPrincipalName)}

    if ($TempContact) {

        Write-Output ("Existing Contact: "+$User.DisplayName)


        # Check for Update using time last changed

        if ($User.Value.WhenModified.addday -gt (get-date).adddays(-2)) {

            Write-Output ("Update Contact: "+$User.DisplayName)

            Update-ExchangeOnlineContact -User $User



    else {

        $TempContact = $SourceExternal|where {$SourceExternal.PrimarySMTPAddress -like $User.UserPrincipalName}

        # Check if external contact, if yes, ignore

        if ($TempContact) {

            Write-Output ("External Contact: "+$User.UserPrincipalName)   


        else {

            $TempDisplay = $SourceDisplay|where {$SourceDisplay.DisplayName -eq ($User.DisplayName + " (Source-Tenant)")}

            # Check, if same Displayname exists

            if ($TempDisplay) {

                # Change Email-Address

                Write-Output ("New Email Address: "+$User.DisplayName+", "+$User.UserPrincipalName)

                Update-ExchangeOnlineContactEmail -User $User


            else {

                # Create new Contact

                Write-Output ("New Contact: "+$User.DisplayName)

                New-ExchangeOnlineContact -User $User






$MySourceUsers|ft UserPrincipalName, DisplayName


# Check Account deletion

ForEach($Contact in $SourceContacts) {

        $TempUser = $MySourceUsers|where {($MySourceUsers.UserPrincipalName) -like $Contact.PrimarySMTPAddress}

        if ($TempUser) {

            Write-Output ("Do not delete: "+$Contact.identity)


        else {

            Write-Warning ("Delete Contact: "+$Contact.identity)

            Write-Output ($Contact.PrimarySMTPAddress)

            $contact|Remove-MailContact -confirm:$false




# Close Session

Get-PSSession | Remove-PSSession


Write-Output "Script Completed!"


You can also use that method with on premise Exchange, but then you have to split the script. One runs with a local agent in your domain, saving the results in a global variable, the other one picks up the content of the variable and runs in azure.
I'seen that pasting the script looses some of the hash characters.
Hope you see whit is comment. :)
On this page, we've implemented B2B, but cannot seem to find any intel on how to share GAL's between our two tenants. We are splitting our organisation, so this is the reason, to easily share the e-mailaddresses between the two existing tenants.
1 best response

Accepted Solutions
best response confirmed by techno1795 (Brass Contributor)
With that said, feel free to vote on the uservoice for sharing GAL between tenants! Quite a big one!

Regards / Adam

View solution in original post