Update User Email Addresses and User Principal Names
A recent reader question asked about the best way to update a bunch of user accounts after the organization buys a new vanity domain and wants the domain used for email addresses and user principal names (sign-in addresses). This sometimes happens when a business goes through a rebranding exercise and ends up with a new name. The requirement to update email addresses and user principal names also occurs in tenant-to-tenant migrations.
Tenant-to-tenant migrations are a specialized kind of activity that’s usually managed with software built for this purpose. We won’t plunge into the challenges that these projects can encounter. Instead, we’ll focus on the scenario where someone in authority decides that all accounts should use different email addresses and user principal names.
Registered Domains
The first requirement is to add the domain to Office 365. Until this is done, you cannot use the domain. Once the domain is known to the tenant, it appears in the set of verified domains seen in the Microsoft 365 admin center (Figure 1).
Figure 1: Verified domains in a Microsoft 365 tenant
After verifying the domain for Microsoft 365, we can write some code to ask the administrator what domain to use. Here’s an example that uses the Get-MgOrganization cmdlet from the Microsoft Graph PowerShell SDK to fetch the verified domains:
Connect-MgGraph -Scopes Directory.Read.All, User.ReadWrite.All
Select-MgProfile Beta
# Get tenant information and the verified domains for the tenant
$TenantInfo = (Get-MgOrganization)
[array]$Domains = $TenantInfo.VerifiedDomains.Name
$DomainsList = $Domains -join ", "
Write-Host "Verified domains for this tenant:"
Write-Host "---------------------------------"
Write-Host ""
$Domains
Write-Host ""
$DomainToUse = Read-Host "What domain do you want to use for primary SMTP addresses and UPNs"
Write-Host ""
If ($DomainToUse -notin $Domains) {Write-Host ("The selected domain ({0}) is not in the set supported by the tenant ({1}). Please try again." -f $DomainToUse, $DomainsList); break }
$CompareDomain = "*" + $DomainToUse + "*"
Finding Mail Recipients
The next step is to find mail-enabled recipients that have email addresses that might need updating. This code finds user mailboxes, shared mailboxes, group mailboxes (for Microsoft 365 groups), distribution lists, and security-enabled distribution lists.
For each object, the code calculates a new primary SMTP address based on their existing address by swapping the existing domain for the new domain. A check makes sure that the new address isn’t already in use, and if it is, creates a new address by adding “.EXO” to the username. The code then checks if it’s necessary to update the user principal name for the Azure AD accounts used by user mailboxes and shared mailboxes. An account might already use the new domain, so the code checks the account’s current user principal name and updates it with the new domain if necessary.
The output is captured in a PowerShell list that’s exported to a CSV file.
[array]$Recipients = Get-Recipient -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox, GroupMailbox, MailUniversalDistributionGroup, MailUniversalSecurityGroup, DynamicDistributionGroup
$i = 0
$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($R in $Recipients) {
$i++
If ($R.PrimarySmtpAddress.Split("@")[1] -ne $DomainToUse) { #Need to process this mailbox
Write-Host ("Processing {0} {1} ({2}/{3})" -f $R.RecipientTypeDetails, $R.DisplayName, $i, $Recipients.Count)
$NewUPN = $Null
# Figure out new email address
$NewAddress = $R.Alias + "@" + $DomainToUse
# Check that the address is available
$Status = Get-Recipient -Identity $NewAddress -ErrorAction SilentlyContinue
# If we get a status the recipient address already exists, so create a new address
If ($Status) { $NewAddress = $M.Alias + ".EXO@" + $DomainToUse }
# Figure out if the account's user principal name needs to change
If ($R.RecipientType -eq "SharedMailbox" -or $R.RecipientType -eq "UserMailbox") {
$UPNDomain = $R.WindowsLiveId.Split("@")[1]
If ($UPNDomain -ne $DomainToUse) { # New UPN needed
$NewUPN = $R.WindowsLiveId.Split("@")[0] + "@" + $DomainToUse
$Status = Get-MgUser -UserId $NewUPN -ErrorAction SilentlyContinue
If ($Status) { # UPN already exists, so create a new one
$NewUPN = $R.WindowsLiveId.Split("@")[0] + ".EXO@" + $DomainToUse }
}
}
$ReportLine = [PSCustomObject] @{
DisplayName = $R.DisplayName
OldUPN = $R.WindowsLiveId
NewUPN = $NewUPN
PrimarySmtpAddress = $R.PrimarySmtpAddress
NewAddress = $NewAddress
Type = $R.RecipientTypeDetails
Alias = $R.Alias
}
$Report.Add($ReportLine) }
}
$Report = $Report | Sort-Object Type
$Report | Export-CSV -NoTypeInformation c:\temp\MailObjects.Csv
Administrators can check the CSV to remove any mail-enabled recipients they don’t want to receive new email addresses (Figure 2).
Figure 2: Editing mail-enabled objects in Excel
Update User Email Addresses with a New Domain
The next step is reads in and processes an array of objects from the updated CSV file.
The code uses a Switch statement to check the object type and calls the appropriate cmdlet to assign the new primary SMTP address to the object. If the account used for a mailbox (user or shared) requires an update for its user principal name, we go ahead and do it.
The final step in the loop through the objects is to report what’s been done, noting the old and new SMTP address and the old and new user principal name.
# Process mail objects array to update primary SMTP addresses and UPNs as necessary
[array]$MailObjects = Import-CSV MailObjects.CSV
$Report = [System.Collections.Generic.List[Object]]::new()
Write-Host "Processing mail-enabled objects..."
$i = 0
ForEach ($M in $MailObjects) {
$i++
Write-Host ("Processing {0} {1} ({2}/{3})" -f $M.Type, $M.DisplayName, $i, $MailObjects.Count)
# Assign new primary SMTP Address
Switch ($M.Type) {
"DynamicDistributionGroup" { # Dynamic distribution list
Set-DynamicDistributionGroup -Identity $M.PrimarySmtpAddress -PrimarySmtpAddress $NewAddress
}
"GroupMailbox" { # Microsoft 365 group
Set-UnifiedGroup -Identity $M.PrimarySmtpAddress -PrimarySmtpAddress $NewAddress
}
"MailUniversalDistributionGroup" { # Distribution list
Set-DistributionGroup -Identity $M.PrimarySmtpAddress -PrimarySmtpAddress $NewAddress
}
"MailUniversalSecurityGroup" { #Mail-enabled security group
Set-DistributionGroup -Identity $M.PrimarySmtpAddress -PrimarySmtpAddress $NewAddress
}
"SharedMailbox" { # Shared mailbox
Set-Mailbox -Identity $M.PrimarySmtpAddress -WindowsEmailAddress $NewAddress
}
"UserMailbox" { # User mailbox
Set-Mailbox -Identity $M.PrimarySmtpAddress -WindowsEmailAddress $NewAddress
}
}
# Update UPN if necessary
If ($M.NewUPN) {
Update-MgUser -UserId $M.UPN -UserPrincipalName $NewUPN }
$ReportLine = [PSCustomObject] @{
DisplayName = $M.DisplayName
OldUPN = $M.UPN
NewUPN = $NewUPN
OldPrimarySmtpAddress = $M.PrimarySmtpAddress
NewPrimarySmtpAddress = $NewAddress
Type = $M.Type
Alias = $M.Alias
}
$Report.Add($ReportLine)
} # End ForEach
Write-Host "All done!"
Figure 3 shows an example of the report that allows administrators to check that the expected email addresses and user principal names are in place.
Figure 3: The updated accounts with new primary SMTP addresses and some new user principal names
The User Issue
Updated user principal names take effect the next time users sign in. If you want to force the switchover, you could disconnect users from their current sessions by invalidating refresh tokens using the Graph revokeSignInSessions API. Invaliding access tokens forces users to reauthenticate and reconnect, and to do that, they must use their new user principal names.
Be aware that some issues exist when changing user principal names such as the need to set up the new user principal name on the Microsoft Authenticator app so that MFA challenges work It’s worthwhile reading through this Microsoft article to understand and test problems that users might encounter in your organization. Knowing what might happen and being prepared to fix the issues will ensure a smoother transition.
Any change to the way people sign-in is likely to cause some angst if it’s not communicated clearly so that everyone understands why the change is happening and what they must do to sign-in to access services.
Tidying Up Azure AD
The process outlined above takes care of the bulk of the work. If some Azure AD accounts that don’t have email addresses need to receive updated user principal names, you can do this with the Update-MgUser cmdlet from the Microsoft Graph PowerShell SDK.
Giving accounts new email addresses and user principal names isn’t a difficult technical challenge. The likely problems arise in preparation and communication. Isn’t that always the way?
Keep up with the changing world of the Microsoft 365 ecosystem by subscribing to the Office 365 for IT Pros eBook. Monthly updates mean that our subscribers learn about new developments as they happen.
11 Replies to “Updating Microsoft 365 User Accounts to use a New Domain”
Keep in mind that changing UserPrincipalName also changes the OneNote url. So if users have OneNote open in the desktop app they need to close the document and open it again with the new url, otherwise changes will not sync.
Hi Tony, can I use this script for hybrid AD objects that live on prem? My users live on-prem but I would like to change everyone’s primary email domain using this script.
I would do some testing before being sure that everything will work as it does online. Remember, the mail attributes for hybrid users must be managed on-premises, and some of the cmdlets used in the script won’t work on-premises.
HI there, THANKS so much for this script! I am getting an error in the final stage when it is updating the UPNs:
Write-ErrorMessage : The email address “.EXO@xxx.com” isn’t correct. Please use this format: user name, the @ sign, followed by the domain name. For example, tonysmith@contoso.com or tony.smith@contoso.com.
At C:\Users\admin\AppData\Local\Temp\tmpEXO_kjc1hqr5.e2o\tmpEXO_kjc1hqr5.e2o.psm1:1120 char:13
+ Write-ErrorMessage $ErrorObject
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Set-DistributionGroup], Exception
+ FullyQualifiedErrorId : [Server=YQBPR0101MB8800,RequestId=14aded9c-f63a-3f95-9c81-c9824889d20e,TimeStamp=Sat, 20 May 2023 07:37:29 GMT],Write-ErrorMessage
The new mail address generated by the script doesn’t seem to include a first name and last name part. You’ll have to debug the script from this point and find out why this is happening.
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
Keep in mind that changing UserPrincipalName also changes the OneNote url. So if users have OneNote open in the desktop app they need to close the document and open it again with the new url, otherwise changes will not sync.
Hello Tony, Thanks for the brief guide. Do you know how many domains can be added, is there any limit ?
I don’t know if there is a limit for accepted domains. I haven’t met one!
Hi Tony, can I use this script for hybrid AD objects that live on prem? My users live on-prem but I would like to change everyone’s primary email domain using this script.
I would do some testing before being sure that everything will work as it does online. Remember, the mail attributes for hybrid users must be managed on-premises, and some of the cmdlets used in the script won’t work on-premises.
Hi it seems like MgUser returns empty in the end, how can i replace that?
“Update-MgUser : Cannot bind argument to parameter ‘UserId’ because it is an empty string.”
Did you fetch user details with Get-MgUser first?
Hello Tony,
Where do i need to fetch the Get MgUser?
HI there, THANKS so much for this script! I am getting an error in the final stage when it is updating the UPNs:
Write-ErrorMessage : The email address “.EXO@xxx.com” isn’t correct. Please use this format: user name, the @ sign, followed by the domain name. For example,
tonysmith@contoso.com or tony.smith@contoso.com.
At C:\Users\admin\AppData\Local\Temp\tmpEXO_kjc1hqr5.e2o\tmpEXO_kjc1hqr5.e2o.psm1:1120 char:13
+ Write-ErrorMessage $ErrorObject
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Set-DistributionGroup], Exception
+ FullyQualifiedErrorId : [Server=YQBPR0101MB8800,RequestId=14aded9c-f63a-3f95-9c81-c9824889d20e,TimeStamp=Sat, 20 May 2023 07:37:29 GMT],Write-ErrorMessage
The new mail address generated by the script doesn’t seem to include a first name and last name part. You’ll have to debug the script from this point and find out why this is happening.