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, Exchange Online Mailboxes, Azure AD applications, etc. The amount of data to migrate impacts how to plan an Office 365 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.
Table of Contents
What does the Office 365 migration plan 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!
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 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!