Gather all the Essential Information from Source Tenants
Assessing the essential information about tenants to for an Office 365 migration plan has never been easy. The complexity involved in tenant-to-tenant migrations has grown considerably as Microsoft adds more features and services to the mix used by tenants. For example, the addition of Shared Channels to Teams adds a new layer of consideration for anyone trying to understand how external people access Teams in an organization.
In addition to Teams, there are many moving parts to track including SharePoint sites, OneDrive for Business, Exchange Online Mailboxes, Azure AD applications, etc. The amount of data that must be migrated impacts the planning of an Office 365 tenant-to-tenant migration, what tools and licensing are needed, and if coexistence is required.
To generate an initial assessment for a tenant, I created two PowerShell scripts and an Excel template (available on GitHub). The first script creates an Azure AD app and certificate; the second script fetches different types of tenant data to create the Office 365 migration plan report. Together, the aim is to capture and document the most important information that influences migration planning. The report helps those working on an Office 365 migration plan to make decisions based on hard data.
What does the Office 365 Migration Report Tell Me?
The script uses cmdlets from the ImportExcel module to create and update an Excel workbook with 19 tabs (worksheets) containing information relevant to migration scenarios. A high-level breakdown of the contents of each tab is detailed here.
Tab 1: High-Level
The High-Level tab works as a cover page and is imported from the template file TenantAssessment-Template.xlsx (available on GitHub). The template file must be present in the directory you run the script from. You can customize the tab by modifying the template (Figure 1), including the use of lookups and formulas to help pull together the high-level information you need into a single place.
When the Office 365 migration plan report runs, this tab will be added, and any lookups will pull information from the report as shown in Figure 2.
This might take some effort to get the format to be just how you like it. The report could even be adapted into a runbook-style sheet that captures common planning tasks and stakeholders.
Preparing for a migration? Learn more in this whitepaper: Top Five Ways to Prepare for Your Next Office 365 Tenant Migration.
Tab 2: User Accounts
The User Accounts tab lists all tenant users and important details of each such as Mailbox/Archive sizes, OneDrive storage consumption, proxy addresses, licenses, and disabled plans. This tab can be used to define the user migration scope. I like to use the Migrate column to record if the user is in scope or not. You can also use the targetObjectID, targetUPN and targetMail columns to help create mapping files for accounts to be used with migration tools (Or remove them from the script if you like!).
Tab 3/4: Shared Mailboxes and Resource Mailboxes
The Shared Mailboxes and Resource Mailboxes tabs give similar information to the User Accounts tab but for Shared, Room, and Equipment Mailboxes.
Tab 5: SharePoint Sites
The SharePoint Sites tab in this Office 365 migration plan lists details and sizes of all SharePoint Online sites that are not connected to Teams.
Tab 6: Teams
The Teams tab provides details of all Teams including a breakdown of Standard/Private/Shared channels and their associated sizes (Figure 3). Shared channels are broken down into standard SharedChannels and IncomingSharedChannels. The difference between these two values is that IncomingSharedChannels are not homed in this Team (created in another Team or even tenant), so don’t count towards data size. This information is useful when assessing the impact of a migration on external access.
Tab 7: Guest Accounts
Guest Accounts are often forgotten when performing an Office 365 migration plan but deserve some consideration. This tab lists all Guest accounts in the tenant. The data can be used with this script to create custom B2B Guest User invitations to recreate guest accounts in the target tenant.
Tab 8: AAD Apps
This tab lists applications which have been registered in the Azure AD tenant (where Microsoft is not the publisher). This information is useful when assessing line of business apps that interact with Office 365.
Tab 9: Conditional Access
The Conditional Access tab lists all Conditional Access policies and settings (Figure 4). The script uses the information gathered about users, groups, and apps to translate Object ID references into easy-to-read names.
Tab 10: M365 Apps Usage
The M365 Apps Usage tab provides a report of the user activity across the different apps over the past 30 days. This report is downloaded from the standard reports available in the Admin Center.
Tab 11: Unified Groups
The Unified Groups tab contains details of all Microsoft 365 Group excluding those linked to Teams.
Tab 12: Standard Groups
This tab contains details of all distribution, security, and mail-enabled security groups in the tenant.
Tab 13: Mail Contacts
This tab contains any mail contacts in the tenant.
Tab 14: MX Records
The MX Records tab details the MX Records for each of the accepted domains known to Exchange Online.
Tab 15: Verified Domains
The Verified Domains tab lists all domains registered in the tenant and the enabled services for each.
Tab 16: Transport Rules
The Transport Rules tab lists all Transport Rules present in Exchange Online and details of each rule’s configuration.
Tab 17/18: Mail Connectors
Tabs 17 and 18 capture the names and settings of each receive and send connector in Exchange Online respectively.
Tab 19: OneDrive Sites
The OneDrive Sites tab contains additional details for each OneDrive for Business account in the tenant, including the URL and recently active file counts.
Office 365 migration plan Powershell assessment prerequisites
The Office 365 migration plan report requires four PowerShell Modules:
- MASL.PS authenticates with the Microsoft Graph API and obtains an Access Token
- ImportExcel exports the report to Excel
- ExchangeOnlineManagement connects to Exchange Online to get information that is not exposed via the Graph API
- AzureAD (or AzureADPreview) sets up the required app registration and permissions
If you don’t already have the required modules installed on a workstation, you can install them from the PS Gallery by running these commands:
Install-Module MSAL.PS
Install-Module ImportExcel
Install-Module ExchangeOnlineManagement
Install-Module AzureAD
In addition to the required modules, the script also requires that the current user account has permission to create and export to the folder c:\temp, and to add certificates to the local user certificate store.
Preparing the environment for an assessment with the Office 365 migration plan
An Azure AD App Registration using Certificate-based authentication authenticate access for the report script to run Microsoft Graph queries. The script Prepare-TenantAssessment.ps1, which is available in the same folder on GitHub, registers the app in Azure AD, assigns the necessary permissions, and creates a Self-Signed Certificate in the current user’s certificate store.
When the preparation script runs, it prompts for Global Admin credentials – these are necessary to create the required objects in the tenant. When the script finishes, a new browser window opens to authenticate with Global Admin credentials. Signing into this page prompts you to grant consent to the new registered app as shown in Figure 5. Once consent is granted, this page redirects to https://localhost and can be closed.
The script displays values for the Tenant ID, Client ID and Certificate Thumbprint (Figure 6). Capture these values for later use (you can always get the information from the app registration overview in Azure AD). Pressing enter clears the display.
The script creates an app registration called Tenant Assessment Tool in Azure AD. The app has all the required read-only roles assigned (Figure 7). If you didn’t grant consent for the app to use these permissions, you should do so here as otherwise, the app won’t be able to access the data it needs to process.
The Service Principal of the app is granted the Global Reader administrative (RBAC) role. This role allows access to information like mail connectors and transport rules that can’t be fetched using a Graph query.
Disable concealed names in reports
As the report uses the Graph usage API, I recommend that you disable the option to Display concealed user, group and site names in all reports in the Reports section of the Microsoft 365 admin center (Figure 8). If this setting is not disabled, the Graph generates obfuscated names when it creates usage data for users, groups, and sites, meaning that the script cannot cross-reference objects.
Running the script for your Office 365 migration plan
The Perform-TenantAssessment.ps1 script requires the ClientID, TenantID and CertificateThumbprint reported by the preparation script to be passed as parameters as shown in Figure 9.
Depending on the number of objects in the tenant the script will take some time to run, particularly in larger environments. When it finishes, the report will be exported to C:\temp\TenantAssessment-<DateTime>.xlsx where <DateTime> is the date/time that the script was run.
This is just the beginning for Office 365 migration plan assessment scripts
A great benefit of sharing scripts like this online is that there is always someone to take a fresh view and suggest or make improvements. This report focuses on the most common characteristics relevant to an Office 365 tenant-to-tenant migration but there’s always more information that can be added. Let us know if you have any good ideas to improve the script by commenting on this article!
Processing task 15 of 28: Connecting to Exchange Online its been over an hour does this take that long?
What prerequisites might I be missing? Just get the following message:
Error preparing script:
One or more errors occurred.
Check Prerequisites
Exiting…
is there an additional pre-req (connect-msolservice, or something)?
I run the .prepare script and get:
Provisioning Entra App Registration for Tenant Migration Assessment Tool
get-mgcontext : The term ‘get-mgcontext’ 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.
At C:\TenantAssessment\Prepare-TenantAssessment.ps1:73 char:12
+ $Context = get-mgcontext
+ ~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (get-mgcontext:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The Microsoft Graph PowerShell SDK is a prereq and contains the command in your error.
Hello Sean,
Thank you for your great work! It seems like the script is not gathering the information any longer for the user mailbox size or anything related to the users. Any idea what could have triggered this?
Thank you!
Vlad
Is it possible to add more detailed information for the SPO sites? I’d like to see list of document libraries and sizes of document libraries? Site data consumption is easy to find from SPO admin center but data consumption and other details per document library would be nice feature
Hi Sean,
really great work! I really appreciate it. It helped us a lot to gather tenant information (also, if you don’t want to migrate it ;-)). It might make sense to add information, if a user/group/Mailbox was created in an on premises Active Directory Services Domain using Azure AD Connect.
We did it for us, but maybe others find this also helpy
Hey Olaf,
Great to hear it helped! Updating to check the sync status sounds like a good addition. I suggest you create a pull request with the additional code so you get the credit for adding it!
Hi, Got this error?
Error exporting report, check permissions and make sure the file is not open! Exception calling “Save” with “0” argument(s): “Error saving file C:\temp\TenantAssessment-1072023 110336 PM.xlsx”
The error is pretty clear. The output file couldn’t be saved. Does the C:/temp folder exist?
Hi,
Did u find the issue i am local admin of temp folder but getting the same error, did u find solution
Will it work if I want to migrate all rules associated with One tenant to another 5 tenants?
Thank you for great content
HI Sean, or anyone else that might hae an issue with this,
You need to update one line in the script, line 122 to:
$Token = Get-MsalToken -ClientId $ClientId -TenantId $TenantId -ClientCertificate $Certificate -ForceRefresh -AzureCloudInstance 1. It Includes the “-AzureCloudInstance 1” at the end. It fixes a problem in the MSAL.
If you get the error “The property ‘Authority’ cannot be found on this object. Verify that the property exists.” The above is you fix.
Source https://github.com/AzureAD/MSAL.PS/issues/45
thanks for this script…very useful in planning
Extremely helpful tools here, saved me a bunch of time. Thanks!
Hi Sean – I’m struggling with the prepare script…. – NB I’m running it in a test tenant… I have no 365 subscription, only an AAD directory, if that’s relevant…
I’m getting the error:
New-AzureADApplication : Error occurred while executing NewApplication
Code: Request_BadRequest
Message: Cannot convert a primitive value to the expected type ‘Edm.Guid’. See the inner exception for more details.
which is thrown by the New-AzureADApplication cmdlet on line 154… I’m assuming it’s a problem with one of the parameters, checking, I see that all the params have a value, the types are:
$appname = String
$appuri = system.array
$permissions = RequiredResourceAccess
$EXOapiPermission = RequiredResourceAccess
Which on the face of it seems all good…
I’m using an account which has Global Admin role (and ONLY that role)
Any thoughts on where I might look to try to get past this?
TIA
Hey Paul,
The problem is likely the Exchange Online permissions with no Office 365 tenant associated. I don’t believe this will work without that
Hi Sean,
Great job. You are a star!!! Highly appreciate what you did and shared.
I have played with your T2T scripts, which work excellent for 3 tenants. I ran it against a fourth one and it created the excel report w/o any errors. The report is missing the MailboxItemCount and MailboxSizeGB data only (empty cells). If I run Get-MailboxStatistics [user] | ft DisplayName, TotalItemSize, I have the correct information for the particular user. All four tests are performed from the same laptop, software/modules versions. Are you aware if there is any variety of O365 tenants? I cannot explain to myself where is the difference and how the same script can fetch the data from three tenants but fails on the fourth. Did you have such an experience?
The item count and sizes come from the reports in 365 so I would check if your fourth tenant has obfuscated names in reports enabled.
You were right! Thank you very much!
I cannot get this to work versus a GCCH environment. What must I do to get this to authenticate properly? I will take a stab at it shortly, but I assume I am missing something easy
Sean, thank you for all of your work. I am running into an issue with the acquiring an access token:
“Unable to acquire access token, check the parameters are correct
The property ‘Authority’ cannot be found on this object. Verify that the property exists.”
Any ideas?
Hi Team,
We have migrated few mailboxes using tenant to tenant approach and we are facing a challenge with AIP encrypted emails. After the mailboxes migrated to the target tenant the recipients can no longer view AIP encrypted emails. We have tried multiple ways and also contacted Microsoft but there is no easy solution. We are also trying to leverage using of MIP API’s to see if the protection labels can be removed before migration but this is not an ideal solution. Have you come across this solution and is there a way we can achieve and help customers who can read the AIP protected emails post migration.
Hi Ahmed,
Without modifying the protection or removing encryption this isn’t possible as far as I know.
Thanks so much for the Sean. I noticed the Export goes through fine but not updating the template… Any Advice?
Hi Dean,
The script outputs to C:\temp\ with a new file. It doesn’t update the template. Make sure the folder exists before running.
Hi Dean,
The template file does not get updated, a second file is created with the output
Hi!
We have a large environment with many objects and the script is failing with the following error: “Invoke-RestMethod : The remote server returned an error: (401) Unauthorized”. The error starts after an hour of script execution, possibly the Access Token is expiring. Could help?
Hi Eduardo,
Yes I imagine if this occurs after an hour then the token is expiring. It must be quite a large environment! You can always add in the Get-Msaltoken command from line 116 where you believe the token is expiring. I’ve run in environments with multiple thousand users without it taking that long but I didn’t come across one of that size.
I am attemtping to run throuh this process to prepare for a tenant to tenant migration. It doesnt look like I am receiving a Certificate Thumbprint from the Prepare Assessement script. Am I doing something incorrectly?
Hi Shekar,
Do you get a certificate created in your personal store? If not you may need to run PowerShell as admin
Thanks for the reply. Checking to see. I did make sure to run Powershell as Admin. I then get this message when I try to run the Perfom Assessement: “Unable to acquire access token, check the parameters are correct
Cannot process argument transformation on parameter ‘ClientCertificate’. Cannot convert the “System.Security.Cryptograph
y.X509Certificates.X509Store” value of type “System.Security.Cryptography.X509Certificates.X509Store” to type “System.Se
curity.Cryptography.X509Certificates.X509Certificate2″.”
This is the error I receive when running as local admin as well as Azure Global admin:
New-AadApplicationCertificate : ERROR. Probably need to run as Administrator.
At C:\Users\user\Desktop\PS\Prepare_Assesement.ps1:196 char:15
+ … humbprint = New-AadApplicationCertificate -ClientId $appReg.AppId -Ce …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,New-AadApplicationCertificate
Was finally able to get it to work. Getting a new error during the Perform. Anyone else see this?
Method invocation failed because [System.Object[]] does not contain a method named ‘op_Division’.
At C:\Users\\desktop\PS\Perform_Assesement.ps1:807 char:9
+ $user.OneDriveSizeGB = (((($OneDrive | ? { $_.’Owner Principa …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Division:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Hi Shekhar,
I have seen that error and will update to account for it. It occurs when one of the values in the Microsoft usage report is blank. You should still have the output file though
Thank you! This has been a huge help! Quick question is the One Drive Data Size in GB as well?
Yes it’s in GB
I tested the script in a small test environment and it worked perfectly, now I am testing it in a real environment with many teams and users and I am getting an error as the script takes hours to run.
Error: Invoke-RestMethod : The remote server returned an error: (401) Not authorized.
Hello,
Thanks for the scripts.
I have an issue with the perform script.
It stops when it tries to connect to Exchange and says there is a new update for the ExchangeOnlineManagement module. I had to install PowerShell 7.2.4 to be able to install the preview5 2.0.5
Now the message is just “Error connecting to Exchange Online…Exiting…”
I think Microsoft probably changed some commands with this new version of the module
I also tested with the latest preview6 2.0.6 with same result.
If you have an idea.
Thanks
Hello,
First, thanks for your scripts.
I’ve an issue running “Prepare-TenantAssessment.ps1”
I have the following message :
“App already exists – Please delete the existing ‘Tenant Assessment Tool’ app from Azure AD and rerun the preparation script to recreate, exiting”
Unfortunately I can’t find the application when I go manually on the Azure web page with the same logins.
If you have an idea ?
Thanks
Make sure you search “All Applications” under “App Registrations” in Azure AD
You can delete this one, I didn’t saw there was a filter activated to only show Enterprise Apps..
Hi,
When I try to Execute the prepare assessment script it give those errors:
En C:\Tenant Assesment\Prepare-TenanatAssessment.ps1: 214 Carácter: 140
+ … rimary text-bold py-2″ data-analytics-event=”{"category":&q …
+ ~
No se permite usar el carácter de Y comercial (&). El operador & está reservado para un uso futuro; encierre un símbolo de Y comercial entre comillas dobles (“&”) para pasarlo como
parte de una cadena.
Token ‘&’ inesperado en la expresión o la instrucción.
No se notificaron todos los errores de análisis. Corrija los errores notificados e inténtelo de nuevo.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : AmpersandNotAllowed
Did you copy and paste the script or modify it? The GitHub script does not have a line 214?
Hi, I get lots of errors when running the prepare script, some examples as below:
ExpandArchiveHelper : Failed to create file ‘C:\temp\microsoft.identitymodel.clients.activedirectory.3.19.8\_rels\.rels’ while expanding the archive file ‘C:\temp\microsoft.identitymodel.clients.activedirectory.3.19.8.zip’ contents as the file ‘C:\temp\microsoft.identitymodel.clients.activedirectory.3.19.8\_rels\.rels’ already exists. Use the -Force parameter if you want to overwrite the existing directory ‘C:\temp\microsoft.identitymodel.clients.activedirectory.3.19.8\_rels\.rels’ contents when expanding the archive file. At C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Archive\Microsoft.PowerShell.Archive.psm1:397 char:17
Looks like this may be related to the AzureAD PowerShell Module you are using. Check that it’s up to date and that it is loading correctly.
Thanks, I upgraded to the latest version and unfortunately still get the issue. I also tried manually connecting via the Azure AD module and that works fine.
Are you by any chance running the script here https://github.com/smcavinue/AdminSeanMc/blob/master/Planner/Prepare-PlannerPowerShell.ps1
This is a Planner Migration Tool which is in the same repository and references those files (“C:\temp\microsoft.identitymodel.clients.activedirectory.3.19.8.zip”)
The folder you want is here: https://github.com/smcavinue/AdminSeanMc/tree/master/Tenant%20Migration%20Assessment
Thanks, this has now ran OK.
Hi
running the script Perform-TenantAssessment.ps1 from (https://github.com/smcavinue/AdminSeanMc/blob/master/Planner/Prepare-PlannerPowerShell.ps1) does not display the required parameters (ClientID, TenantID and CertificateThumbprint ) to run Perform-TenantAssessment.ps1.
Thanks for the tips. There were no errors with -Verbose and I was able to run Connect-AzureAD no problem.
Took me a couple of tries but now it’s working great.
Sequence of events:
1- Removed the requires statement to fix the error I posted.
2- All was good until I got this message:
“Unable to acquire access token, check the parameters are correct
The property ‘Authority’ cannot be found on this object. Verify that the property exists.”
3- Switched to a different workstation and started again. This time no errors encountered, not even the original one, and even though I had put the “Requires” statement back in as a test.
4- Script appeared to be executing correctly until it detected that WinRM basic auth was disabled on workstation.
5- No quick way to fix on that workstation so switched to a 3rd WS where basic auth was already enabled.
6- Script executed flawlessly from beginning to end of your whole documented process and produced a detailed inventory of my tenant.
So, had to jump through a couple of hoops but well worth it for the learning and the end result, which is a fantastic piece of documentation.
Once again, thanks for releasing this to the community.
Good day and thank you for all the hard work here.
Look forward to using the script but I seem to have fallen at the first hurdle:
PS C:\temp\PowerShell> Install-Module AzureAD
PS C:\temp\PowerShell> Install-Module ExchangeOnlineManagement
PS C:\temp\PowerShell> Install-Module ImportExcel
PS C:\temp\PowerShell> Install-Module MSAL.PS
PS C:\temp\PowerShell> .\Prepare-TenantAssessment.ps1
.\Prepare-TenantAssessment.ps1 : The script ‘Prepare-TenantAssessment.ps1’ cannot be run because the following modules
that are specified by the “#requires” statements of the script are missing: AzureAD.
At line:1 char:1
+ .\Prepare-TenantAssessment.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (Prepare-TenantAssessment.ps1:String) [], ScriptRequiresException
+ FullyQualifiedErrorId : ScriptRequiresMissingModules
All required modules appeared to install without error.
Any help to understand what I have missed is appreciated.
Thanks!
The requires statement checks if the AzureAD module is installed but seems to not see the module on your machine.
Do you get any errors running: “Import-Module AzureAD -Verbose”
You could also check if you can run Connect-AzureAD to test the module if so then you can try run it after removing the Requires statement (line 22).
Thanks for the tips. There were no errors with -Verbose and I was able to run Connect-AzureAD no problem.
Took me a couple of tries but now it’s working great.
Sequence of events:
1- Removed the requires statement to fix the error I posted.
2- All was good until I got this message:
“Unable to acquire access token, check the parameters are correct
The property ‘Authority’ cannot be found on this object. Verify that the property exists.”
3- Switched to a different workstation and started again. This time no errors encountered, not even the original one, and even though I had put the “Requires” statement back in as a test.
4- Script appeared to be executing correctly until it detected that WinRM basic auth was disabled on workstation.
5- No quick way to fix on that workstation so switched to a 3rd WS where basic auth was already enabled.
6- Script executed flawlessly from beginning to end of your whole documented process and produced a detailed inventory of my tenant.
So, had to jump through a couple of hoops but well worth it for the learning and the end result, which is a fantastic piece of documentation.
Once again, thanks for releasing this to the community.
This looks excellent, thanks Sean! Looking forward to giving it a test drive.
Sean, great work, I appreciate the hard work you’ve done to make this happen!
Thanks for your huge work !
First feedbacks:
– User accounts => Missing data from the sizes while I have a mailbox at least (values are ok into shared mailboxes sheet)
– Conditional Access => I think the construction of the table has an issue. I see PolicyName and the name of my policy as column.
– M365 Apps Usage => Special caracter in the name of the first column
Thanks Julian,
On the user accounts data, I have not seen this but as we use the Mailbox usage reports can you check if the user is missing from there also?
Conditional Access – If I understand correctly you have a column for each policy and settings along the left (first) column. This is by design to try make it easier to read
Apps usage – I did notice that also but it is a default Microsoft report It could be tidied up by trimming the variable
When I try to Execute the prepare assessment script it give me an error:-
Waiting for app to provision…
Error creating new app reg:
Cannot bind argument to parameter ‘ObjectId’ because it is null.
Exiting…
Hi Kannan,
Seems like it’s failing as the global reader role is not provisioned in your tenant. You should be able to assign the global reader role to any user then rerun. I may update with a check for this soon.
Script is now updated to provision the role if it’s missing so if you redownload it it should work even when you don’t already have the role provisioned.
Thank you so much Sean, Cheers!