Automating Azure AD B2B Collaboration with PowerShell

In this article, we are going to go through a scenario where we automate Azure AD B2B external sharing using PowerShell. With the given approach, you can invite external users from one Office 365 tenant to collaborate in another tenant's Office 365 Group or SharePoint Online site. You can quite easily alter the solution to other needs, like inviting users from multiple tenants, or giving them permission to access multiple groups or sites, or do something a bit different such as inviting external users to your Azure AD Applications instead of Office 365 sites and groups.

The real life scenario behind this solution is a city with two Office 365 tenants, one Office 365 Government for regular city personnel and one Office 365 Education for teachers, other school personnel and students. The government tenant holds a SharePoint Online site for city intranet, teachers and school personnel needs access to this site. This is done using the solution described here. Some parts of the solution are modified to better fit my Office 365 Engage 2017 presentation and this article.

High level overview of the solution

At high level the solution consists of two parts:

  1. Reading source tenant for the users we want to invite to target tenant. Guest users will be saved to CSV files.
  2. Inviting guest users in CSV files as Azure AD Guests to the target tenant and granting them access to Azure AD security group or in Office 365 Group.

Full source code and dependencies

The full source code is available via GitHub. This solution requires that you have installed the following three modules:

  1. MSOnline v1 commandlets are used in the first part of the solution. For more information, click here.
  2. Azure Active Directory V2 (Public Preview Release commandlets are used in the second part of the solution. For more information, click here.
  3. Credential Manager 2.0 to access Windows Credential Manager. For more information, click here.

Part 1: Reading selected users from source tenant into CSV files

First part of the solution uses two PowerScript files as well as on XML configuration file, the files are:

  1. Invite-ExternalUsers.ps1 is the main "program", designed to be run as scheduled windows task.
  2. Functions.ps1 is a collection of PowerShell functions used in the main script.
  3. UsersCSVFile.config.xml is the configuration file.

Configuration file

In order to understand this part of the solution, let's start by taking a look at key parts of the configuration file.

To keep everything clean, I'm using stored credentials, with Credential Manager 2.0 you can easily save credentials and access those credentials in your PowerShell code. Administrative username and password is always needed when you run your PowerShell in scheduled fashion. Instead of saving the admin password to a file, which, we are simply referring to the stored password with a key.


Second important section of the configuration file is called includeFilters. The solution has been designed to support both "all users" and "members of these groups" approach. The first one is really intended only to be used in quick testing, while the latter is the real way to go in production solutions. You define the groups by stating the group names in XML. I have tested the solution using security groups and it works with both Azure AD security groups as well as security groups synced from on-premises AD.

<groups> <group>AD Group Name 1</group> <group>AD Group Name 2</group> </groups>

Final, important section of configuration file is called excludeFilters. In this solution, we first create a list of all users who are members in at least on of the groups. But sometimes we want to exclude some of the users. The solution supports two kinds of excluding actions.

First, we can exclude users by UserType type. For example we can ensure that no Guest users of the source tenant are invited.

<excludeFilter> <property>UserType</property> <value>Guest</value> </excludeFilter>

Second, we can exclude users who's UPN contains certain strings. This allows us to exclude service accounts and specific users.

<excludeFilter> <property>UserPrincipalName</property> <containsAny>_svc;sync_;admin@;ext-</containsAny> </excludeFilter>

Now, let's take a look at the key parts of the main script, Invite-ExternalUsers.ps1.

First we are loading Functions.ps1. This script file includes ConvertTo-B2BCsv, ExcludeBy-UserType and ExcludeBy-UserPrincipalName as well various logging related functions. Then we are reading the XML configuration and connecting the Microsoft Online service.

Then we are either reading all groups in line 18 $users = Get-MsolUser -All or creating an empty array of users and reading all the groups in Azure AD in the lines 20 and 21.

$users = @() $groups = MSOnlineExtended\Get-MsolGroup -All

Note: the tenants I have used this script are relatively small, only a few thousand users and similar amount of groups. In these situations, the given way to use -All switch works, but it is not going to be a good solution if the tenant you are working with is considerably larger.

After that, the code will loop through all the groups in configuration file. It will fetch the group information in line 25 $g = $groups | Where-Object { $_.DisplayName -eq $item.InnerText } and read all group members in the line 30 $groupMembers = Get-MsolGroupMember -GroupObjectId $g.ObjectId. In another loop in lines 31 - 36, we are adding the user to users array unless he or she was already added when we iterated through different group.

In another loop in lines 57 - 80 we are excluding users based on excludeFilters.

Finally the script will create the CSV-files by calling ConvertTo-B2BCsv function. At this point CSV-files are stored in the working folder, and with a bit of error handling the script ends.

Part 2: Inviting guest users

Second part of the solution uses the following files:

  1. Invite-ExternalUsers.ps1 which is our main script, intented to be run some time after the CSV files are created.
  2. Functions.ps1 is the same file as in part 1.
  3. ExternalUsers.config.xml is the configuration file for Invite-ExternalUsers.ps1.
  4. CSV files containing guest user information.

Again, starting form the configuration file. Again, we need credentials, but this time for the target tenant.


We need to know the TenantID. An easy way to retrieve the information is simply call Connect-AzureAD without any parameters and login with you admin account. After you connect, the resulting object will show the TenantID.


Then we define an URL which will be part of the invite, the actual Office 365 Group or SharePoint site where Azure AD will redirect the guest after successful authentication.

Then we define the Azure AD Group or Office 365 Group into which we will add the guest users.


Finally we can do some customization on the invite, part of the invite text can be defined in this section. Unfortunately you can't use HTML markup in the message.

<customizedMessageBody>Customized message here</customizedMessageBody>

Finally let's walk through Invite-ExternalUsers.ps1, the script that actually invites the guests from the CSV files.

This script starts in the same fashion, first executing Functions.ps1 and then reading the XML configuration. After that, the script connects Azure AD in line 20.

$connection = Connect-AzureAD -Credential $credentials -TenantId $configXml.config.tenantId

In lines 27 - 33, the script is creating and configuring Microsoft.Open.MSGraph.Model.InvitedUserMessageInfo object which will be used as a parameter in the invitation. We are using customized message here, and you can add an administrator as CC in the mail if you want to be able to access or archive the invite to the administrator's mail box as well. Just don't use an account which you really use, because you can easily flood your emails with this.

Then we are going to fetch AD Group information for the group we are going to add the guest users. We need the access to both group and it's members. You can use both AD Security and Office 365 Groups here although the variable name is $securityGroup.

$securityGroup = Get-AzureADGroup -SearchString $adGroupToAddExternalUser $members = Get-AzureADGroupMember -ObjectId $securityGroup.ObjectId -all $true

Lines 40 - 47 are for error handling, if we were unsuccessful in the attempt to retrieve group and group member information, the script can't continue and we are just logging the errors and exiting.

The loop starting at the line 59 will iterate through all CSV files. It will go through all users in all of the files, and if there is an user that is not already a member of the group, we are inviting the user in line 83.

$invitation = New-AzureADMSInvitation -InvitedUserDisplayName $userName -InvitedUserEmailAddress $email -SendInvitationMessage $true -InvitedUserMessageInfo $messageInfo -InviteRedirectUrl $inviteRedirectUrl

And then adding the the user to the group in line 86.

Add-AzureADGroupMember -ObjectId $securityGroup.ObjectId -RefObjectId $invitation.InvitedUser.Id

All processed CSV files are moved to archive folder in the line 99.

We are running these scripts on daily bases, which allows us to invite new Education tenant users to access the city intranet. Currently we don't have automated cleanup, which would remove the users which no longer exist in the Education tenant. Those users remain in the group but cannot actually access the site because Education tenant's Azure AD wont authenticate them.