Are Azure AD Guest Accounts the Better Option?
A discussion about removing stale Azure AD guest accounts with unredeemed discussions led to an assertion (not by me) that Microsoft 365 organizations are better off if they replace the mail contact objects used by Exchange Online with guest accounts. The logic being that Microsoft puts a lot of engineering effort into developing and maintaining guest accounts as part of Azure AD External Identities. Tenants benefit from the improvements Microsoft makes to the functionality and lifecycle management for external identities instead of accepting mail contacts for the simple objects that they are (I compare mail objects and guest accounts in another article).
If you accept the proposal that guest accounts are the better option, the next question is how to migrate mail contacts to guest accounts. Microsoft doesn’t offer a migration option, and I can’t find a suitable utility elsewhere, so the only choice is to build your own with PowerShell.
Conceptually, the job isn’t difficult. The script must find mail contacts to process and use the data to create new guest accounts. Here’s what I did.
On Demand Migration
Migrate all your workloads and Active Directory with one comprehensive Office 365 tenant-to-tenant migration solution.
Finding Contact Data
You can use the Get-MailContact cmdlet to find mail contacts (the logical choice), but the Get-ExoRecipient cmdlet returns additional organizational information that helps to build out the properties of the guest account. This can be confusing, but it’s explained by:
- Exchange Online and Azure AD both store contact objects. Exchange and Azure AD synchronize object settings.
- The Get-MailContact cmdlet returns properties from the Exchange contact.
- The Get-ExoRecipient cmdlet returns properties from the Azure AD contact together with some email properties.
The UI of the Microsoft 365 admin center and Exchange admin center disguise the fact that two different objects exist. Figure 1 shows an example of a contact as displayed by the Microsoft 365 admin center. The Settings tab allows the administrator to update the email properties of the Exchange object, while the general tab allows access to organizational information stored in Azure AD.
Creating Azure AD Guest Accounts
After finding contacts, the script loops down through the set to create guest accounts. First, it checks if the email address used by the mail contact already exists for a guest account. Normally, Exchange Online doesn’t allow multiple mail-enabled recipients to share the same email address. Guest accounts are an exception because Exchange Online creates special mail user objects for guest accounts (see this article for details).
Next, the script assembles the properties for the new guest account. Be aware that the cmdlets in the Microsoft Graph PowerShell SDK have some interesting foibles (some would say unexplainable bugs) that need consideration. For instance, you can’t use the PowerShell $Null variable as an empty value to write into a property because SDK cmdlets only accept space characters.
Guest accounts have passwords, so the script needs to create a password (even if no one ever signs into the guest account). I use a similar routine to that explained in this article about creating new Azure AD user accounts.
To create the new guest account, the script runs the New-MgUser cmdlet and passes the hash table of properties populated from the source mail contact. It then runs the Update-MgUser cmdlet to make sure that the guest account (or rather, the guest mail user created by Exchange Online for the guest account) appears in Exchange address lists like the OAB and GAL. The script puts the source mail contact into an unused state by assigning it a dummy email address and hiding it from Exchange address lists. It also marks the contact as migrated.
# Populate hash table with properties for the new account $NewUserProperties = @{ UserType = "Guest" GivenName = $FirstName Surname = $LastName DisplayName = $DisplayName JobTitle = $JobTitle Department = $Department MailNickname = $NickName Mail = $Contact.PrimarySmtpAddress UserPrincipalName = $UPN Country = $Country City = $City PostalCode = $PostalCode OfficeLocation = $Office Company = $Company UsageLocation = $UsageLocation PasswordProfile = $NewPasswordProfile AccountEnabled = $true } # Try to create new guest account Try { $NewGuestAccount = New-MgUser @NewUserProperties } Catch { Write-Host ("Couldn't create new Guest account using these properties") Write-Host $NewUserProperties Break } # Let the new guest appear in Exchange address lists Update-MgUser -UserId $NewGuestAccount.Id -ShowInAddressList # Hide the mail contact and keep a record of the old email address (that the guest account now has Set-MailContact -Identity $Contact.Alias -HiddenFromAddressListsEnabled $True -CustomAttribute1 $Contact.PrimarySmtpAddress -CustomAttribute2 "Migrated"
It takes a day or so before the new guest account shows up in the OAB. This is because Exchange must generate change files for clients to process to make the changes visible. After this happens, the guest account created from the migrated contact looks like any other contact when viewed through Outlook (Figure 1) or OWA (which displays photos when available).
Updating Distribution Lists With Azure AD Guest Accounts
Many mail contacts are members of distribution lists. To replace the contacts in a list membership, the script finds the set of distribution lists that each mail contact belongs to and writes this information out to a list. It would be nice to update the distribution lists immediately, but this requires the special guest user object created by Exchange Online for a guest account. Exchange Online recognizes the guest mail user as a valid email recipient, so that’s why it’s the object added to distribution lists.
However, the creation of the guest mail user object is not immediate. It depends on a background process that can take up to five minutes. This is why the script writes the information about membership to a list:
$DN = $Contact.DistinguishedName [array]$DLs = Get-ExoRecipient -ResultSize Unlimited -Filter "Members -eq '$DN'" -RecipientTypeDetails MailUniversalDistributionGroup -ErrorAction SilentlyContinue If ($DLs) { Write-Host ("User is a member of {0} groups" -f $DLs.count) ForEach ($DL in $DLs) { $DataLine = [PSCustomObject] @{ DLName = $DL.DisplayName DLAlias = $DL.Alias DLId = $DL.ExternalDirectoryObjectId DLOldMember = $DN DLNewMember = $NewGuestAccount.Id } $DLUpdates.Add($Dataline) } } #End If $DLs
After processing all contacts, the script writes the distribution list updates to a CSV file. Later, a separate script can import the details from the CSV file and process the updates by removing source contacts from distribution lists and replacing them with the guest mail users:
[array]$DLUpdatesToProcess = Import-CSV c:\temp\DLUpdateToProcess.csv ForEach ($Update in $DLUpdatesToProcess) { Remove-DistributionGroupMember -Identity $Update.DLAlias -Member $Update.DLOldMember -Confirm:$False Add-DistributionGroupMember -Identity $Update.DLAlias -Member $Update.DLNewMember }
Contact Clean Up
Later, we should clean up by removing the migrated contacts, not least it’s best to have one instance of the contact to manage. As shown in Figure 3, if you don’t, the Exchange admin center and other management interfaces will include both the old source mail contact and the new guest mail user object when viewing contacts.
Maintenance and Care of Guest Accounts
I am sure that I have forgotten some steps that the script could perform, but those listed above are sufficient to create guest accounts to prove that they can serve the same purpose as mail contacts. It’s important to realize that once you make the switchover to guest accounts, the Microsoft Entra admin center becomes the fulcrum for future maintenance of your contacts. The Exchange admin center can display guest mail user objects, but it can’t update them.
Figure 4 shows a guest account created from a migrated contact. I’ve added a photo to the account. This is an added value that you get from guest accounts, as Exchange Online doesn’t allow you to add photos to mail contacts.
One immediate issue to deal with if you replace mail contact with guest accounts is that the Azure AD admin center cannot create guest accounts without sending an invitation to the guest’s email address. This is natural and by design. The admin center supports Azure B2B collaboration, and invitation redemption is the mechanism for guests to prove their identity and gain access to tenant resources stored in Teams, SharePoint Online, and OneDrive for Business.
But if I’m creating a new guest account instead of a mail contact, I don’t necessarily want to send an invitation if the guest account is to serve as a pointer for collaboration over email rather than the type supported by Teams and SharePoint Online. If the guest receives an invitation, they can respond to redeem the invitation, but they won’t be able to access any tenant resources, so it’s somewhat of a no-op. It would be nice if the Azure AD admin center supported the creation of basic guest accounts without invitations, as I doubt that everyone will be happy to run the New-MgUser cmdlet to create new guest accounts.
Microsoft Platform Migration Planning and Consolidation
Simplify migration planning, overcome migration challenges, and finish projects faster while minimizing the costs, risks and disruptions to users.
Moving to Guest Accounts
Moving from mail contacts to guest accounts is possible (you can download a script from GitHub to see how I did it). It’s a manual process that isn’t difficult but needs some care. Not being able to create guest accounts without invitations through the admin center is a small pain, and it’s evidence that Microsoft hasn’t done the work yet to prepare the infrastructure to allow organizations to move away from email-centric mail contacts to an object that’s more in tune with the Microsoft 365 ecosystem. Perhaps that work will come over time.
The Real Person!
The Real Person!
This converts the mail contact to a guest user and then replaces the mail contact in a DL with the guest user.
Does this then make the DLs ready for Group upgrade?
Is this section still active? i have few queries . Please let me know if i can post my query
Hey Tony
In your code, when you create a Guest User, it deposits the UPN in de proxyaddress, why is this?
I can’t find a solution…
$NewUserProperties = @{
UserType = “Guest”
GivenName = $FirstName
Surname = $LastName
DisplayName = $DisplayName
JobTitle = $JobTitle
Department = $Department
MailNickname = $NickName
Mail = $Contact.PrimarySmtpAddress
UserPrincipalName = $UPN
The UPN comes from $UPN = $NickName + “#EXT#@Office365itpros.com”
The primary SMTP address comes from the migrated contact’s email address.
What issue are you seeing?
The Guest User is getting 2 entries in the proxy address:
smtp: UPN-Address
and
SMTP: external address
I want to get rid of the UPN in the proxy, but didn’t find a solution yet…
Do you mean that Mail User object or the User account?
The Guest Mail User that you create in the script, but I just found out, with:
Update-MgUser -UserId $NewGuestAccount.Id -ShowInAddressList:$True
The UPN appears in the proxyAddress…
Now im trying to find out how i can avoid that and still show the user in the address list.
Set-MailUser -Identity $UPN -EmailAddress $PrimarySMTPAddress
Thanks for the help, but that doesn’t work, the UPN is still registered as proxyaddress. Maybe there is a way to delet it?
If you overwrite EmailAddresses with the PrimarySMTPAddress, that’s the only address that will be there.
I found a solution, thanks for everything
Okay, I want to test the script with a test User/MailContact, later then I want to it on more Users. I don’t want to run the Script immediately because of a failure, it wouldn’t be good for us.
Now I want to know, how can I specify the script for only one user, I didn’t test it yet but something like that:
[array]$GuestEmail = Get-MgUser -All -Filter “userType eq ‘Guest'” -UserId ‘TestUserId’ | Sort-Object Mail | Select-Object -ExpandProperty Mail
And a other question, do the Guest Users need a Passoword?
Change:
[array]$GuestEmail = Get-MgUser -All -Filter “userType eq ‘Guest’” -UserId ‘TestUserId’ | Sort-Object Mail | Select-Object -ExpandProperty Mail
to:
[array]$GuestEmail = Get-MgUser -userId ‘UPN of guest account’ | Sort-Object Mail | Select-Object -ExpandProperty Mail
This will select just the guest account with the UPN you specify.
Thank you for the replies,
Now what I only have changed is:
[array]$Contacts = Get-ExoRecipient -Identity (Guest Email) -RecipientTypeDetails MailContact -ResultSize Unlimited -PropertySets All
[array]$GuestEmail = Get-MgUser -userId (UPN MAIL) | Sort-Object Mail | Select-Object -ExpandProperty Mail
$UPN = $NickName + “(UPN….)”
Now my last questions:
1. on the first else command , do the Guest Users really need a Password? or can I Comment the Password commands?
2. $NewPrimarySmtpAddress = $NickName + “(???what do you mean with different smtp address?, where is the @)”
Guest accounts don’t need a password, but the New-MgUser cmdlet requires passing a password profile as a parameter.
The code is:
$NewPrimarySmtpAddress = $NickName + “.temp@Office365itpros.com”
Set-MailContact -Identity $Contact.Alias -EmailAddresses $NewPrimarySmtpAddress
This is to give the mail contact a temporary SMTP address so that you can assign the address it had to the guest account. Obviously you’d use your own domain rather than office365itpros.com
Hm, When I run the script, it says: Connect-MgGraph : The term ‘Connect-MgGraph’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if
a path was included, verify that the path is correct and try again.
Even though I have installed the Module…
Hey Tony
Im not an expert with this topic, could you explain me what should I change in the script?
And another thing, what should I change for for testing a specific user or users, that i wont apply everything on the productive environment.
You’ll have to be more specific about exactly what you want to change in the script.
Hi Tony, Please could I check the effects of the Legacy Exchange DN on the mail contact. Would this need to be need to be transferred over to the new guest account (assuming its been enabled to be present in the GAL) to prevent NDR’s where the contact has been removed?
You can’t transfer the legacy Exchange DN to a guest account. Why would you use a legacy DN to address email? Can you construct a scenario to test?
The question was based on experiences with Exchange On-Prem where I recall the re-creation of mail enabled objects required the legacyexchangedn to be retained and applied as an X500 proxy address in scenarios where maybe a mailbox was re-created or where a mail user was deleted and could not be restored with the attributes including legacyexchangedn.
I believe this was based on the Outlook NK2 cache.
A quick scenario test using OWA did not produce an NDR where a contact was deleted and replaced with a mail enabled “mail user” in EXO. I will test further with Outlook, but do you have any details about any changes to the way mail enabled objectd are addressed in EXO compared with Exchange On-Prem? Is the legacyexchangeDN no longer an issue in EXO?
Exchange on-premises had to retain the legacy DN for reasons that don’t exist in the cloud, like backward compatibility with email generated with the first generation of Exchange, which had its own X.500-based directory store. I haven’t seen Outlook attempt to use a DN address for years. If I saw one, I’d want to investigate where it came from…
A key question for me here is the lifecycle of guests. It’s likely in this example the guest users will never login yet the mail user object will likely be used in regular email communications. If the guest object is removed based on a lastlogon timestamp for example, I assume users can expect the typical legacy exchange DN NDR?
Then the question is what should my lifecycle for contacts (and DL) be? We can expire M365 Groups but DLs and Contacts have always been tricky. Back in the day we would add a timestamp based on the messaging logs but I haven’t seen anything for Exchange Online custom or otherwise?
That said the new cross tenant sync for guest accounts is a good option for using guests as mail users where lifecycle is baked into the product.
You could always write the creation date into one of the 15 custom properties available for guest accounts…
And you can track email sent to guest accounts via message traces because these capture the primary SMTP address for the guest account.
yes agreed… have you seen any scripts like this publiclly available? if not maybe I try and book in some time to do it. 😉
I think you may have mixed up the terms mail contact and mail user, you talk about migrating contacts which are not users(security principals) but the source commands are all about migrating mail users which are security principals with an external email address and are mail enabled. They are fundamentally different objects
No, I didn’t mix them up.
The article is about migrating mail contact objects (Exchange Online) to guest accounts (Azure AD). A consequence of the migration is that mail user objects are created in Exchange Online. If you look at the code in the script, you can see that the Get-ExoRecipient cmdlet fetches mail contact objects that haven’t yet been migrated.
[array]$Contacts = Get-ExoRecipient -RecipientTypeDetails MailContact -ResultSize Unlimited -Filter {CustomAttribute2 -ne “Migrated”} -PropertySets All
Each contact is processed, and the New-MgUser cmdlet creates a guest account based on the contact properties.
A side effect is the creation of a matching mail user account for each guest account. That’s why mail user objects are mentioned in the article. I might have overemphasized this point, if only because if you look at the Contacts section of EAC after the migration, you’ll see a bunch of mail user objects (type=GuestMailUser) listed. I don’t know why Microsoft lists these objects in EAC because you can’t edit them there.