Managing multiple Microsoft 365 tenants can be a complex and time-consuming task for IT administrators. As organisations grow and evolve, the need for efficient tenant management becomes paramount. In this article, we’ll explore how PowerShell can be leveraged to streamline Microsoft 365 tenant management, with practical examples and best practices.
The Challenge of Multi-Tenant Management
For organisations with multiple subsidiaries, international offices, or those undergoing mergers and acquisitions, managing separate Microsoft 365 tenants is often a necessity. However, this can lead to several challenges:
- Inconsistent configurations across tenants
- Time-consuming repetitive tasks
- Difficulty in maintaining security standards
- Challenges in reporting and compliance
PowerShell offers a powerful solution to these challenges, allowing administrators to automate repetitive tasks, ensure consistency, and maintain robust security across all tenants.
Getting Started with Microsoft 365 PowerShell Modules
Before diving into specific scripts, it’s essential to understand the key PowerShell modules for Microsoft 365 management:
## Install the Microsoft Graph PowerShell SDK
Install-Module Microsoft.Graph -Scope CurrentUser
## Install the Microsoft 365 module
Install-Module -Name Microsoft365DSC -Scope CurrentUser
## Install Azure AD module (still needed for some operations)
Install-Module -Name AzureAD -Scope CurrentUser
These modules provide comprehensive cmdlets for managing various aspects of Microsoft 365, from user management to security configurations.
Automating Tenant Health Checks
One of the most valuable applications of PowerShell for tenant management is automating regular health checks. Here’s a script that performs key health checks across multiple tenants:
## Define the tenants to check
$tenants = @(
@{Name = "Contoso"; AdminUser = "admin@contoso.onmicrosoft.com"; TenantId = "contoso-guid"},
@{Name = "Fabrikam"; AdminUser = "admin@fabrikam.onmicrosoft.com"; TenantId = "fabrikam-guid"}
)
## Create an array to store results
$healthCheckResults = @()
foreach ($tenant in $tenants) {
Write-Host "Checking tenant: $($tenant.Name)..." -ForegroundColor Cyan
# Connect to Microsoft Graph with the appropriate credentials
Connect-MgGraph -TenantId $tenant.TenantId -Scopes "Directory.Read.All", "Policy.Read.All", "Reports.Read.All"
# Check service health
$serviceHealth = Get-MgServiceAnnouncement -Filter "ServiceHealth eq 'Incident'"
# Check for accounts with MFA disabled
$usersWithoutMFA = Get-MgUser -All | Where-Object {
$authMethods = Get-MgUserAuthenticationMethod -UserId $_.Id
-not ($authMethods | Where-Object {
$_.AdditionalProperties."@odata.type" -match "microsoft.graph.microsoftAuthenticatorAuthenticationMethod|microsoft.graph.phoneAuthenticationMethod"
})
}
# Check for unused licenses
$licenseUsage = Get-MgSubscribedSku | Select-Object SkuPartNumber, ConsumedUnits, PrepaidUnits
# Add results to the array
$healthCheckResults += [PSCustomObject]@{
Tenant = $tenant.Name
ServiceIncidents = $serviceHealth.Count
UsersWithoutMFA = $usersWithoutMFA.Count
LicenseUtilization = $licenseUsage | ForEach-Object {
"$($_.SkuPartNumber): $($_.ConsumedUnits)/$($_.PrepaidUnits.Enabled)"
}
}
# Disconnect from Microsoft Graph
Disconnect-MgGraph
}
## Output the results in a formatted table
$healthCheckResults | Format-Table -AutoSize
This script connects to each tenant, performs health checks for service incidents, MFA adoption, and license utilization, then outputs a concise report.
Standardising Configurations Across Tenants
Another powerful use case is ensuring consistent configurations across all tenants. The following script demonstrates how to standardize security baseline settings:
## Define security baseline settings
$securityBaseline = @{
PasswordPolicies = @{
PasswordExpiration = 90 # days
MinimumLength = 12
ComplexityEnabled = $true
}
ConditionalAccess = @{
BlockLegacyAuthentication = $true
RequireMFAForAllUsers = $true
}
SharePointSettings = @{
DefaultSharingLink = "Internal"
SharingCapability = "ExternalUserSharingOnly"
}
}
## Process each tenant
foreach ($tenant in $tenants) {
Write-Host "Configuring security baseline for tenant: $($tenant.Name)..." -ForegroundColor Green
# Connect to services
Connect-MgGraph -TenantId $tenant.TenantId -Scopes "Policy.ReadWrite.All", "Directory.ReadWrite.All"
# Configure password policies
$passwordPolicy = @{
PasswordValidityPeriodInDays = $securityBaseline.PasswordPolicies.PasswordExpiration
MinimumPasswordLength = $securityBaseline.PasswordPolicies.MinimumLength
PasswordComplexityEnabled = $securityBaseline.PasswordPolicies.ComplexityEnabled
}
Update-MgDirectorySettingByDisplayName -DisplayName "Password Policy" -Values $passwordPolicy
# Configure Conditional Access policies
if ($securityBaseline.ConditionalAccess.BlockLegacyAuthentication) {
# Create policy to block legacy authentication
$conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet
$conditions.ClientAppTypes = New-Object -TypeName System.Collections.Generic.List[String]
$conditions.ClientAppTypes.Add("ExchangeActiveSync")
$conditions.ClientAppTypes.Add("Other")
$controls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls
$controls._Operator = "OR"
$controls.BuiltInControls = New-Object -TypeName System.Collections.Generic.List[String]
$controls.BuiltInControls.Add("Block")
New-MgIdentityConditionalAccessPolicy -DisplayName "Block Legacy Authentication" `
-State "Enabled" -Conditions $conditions -GrantControls $controls
}
# Configure SharePoint sharing settings
if ($tenant.Name -eq "Contoso") {
# SharePoint settings require the SharePoint Online module
Connect-SPOService -Url "https://contoso-admin.sharepoint.com"
} elseif ($tenant.Name -eq "Fabrikam") {
Connect-SPOService -Url "https://fabrikam-admin.sharepoint.com"
}
Set-SPOTenant -DefaultSharingLinkType $securityBaseline.SharePointSettings.DefaultSharingLink `
-SharingCapability $securityBaseline.SharePointSettings.SharingCapability
# Disconnect from services
Disconnect-MgGraph
Disconnect-SPOService
Write-Host "Security baseline applied successfully to $($tenant.Name)" -ForegroundColor Green
}
This script applies consistent security settings across multiple tenants, ensuring that password policies, conditional access rules, and SharePoint sharing settings meet your organisation’s standards.
Reporting Across Tenants
Generating comprehensive reports across multiple tenants is another valuable capability of PowerShell. The following script creates a consolidated report of guest users across all tenants:
## Create an array to store guest user information
$allGuestUsers = @()
foreach ($tenant in $tenants) {
Write-Host "Gathering guest users from tenant: $($tenant.Name)..." -ForegroundColor Yellow
# Connect to Microsoft Graph
Connect-MgGraph -TenantId $tenant.TenantId -Scopes "User.Read.All"
# Get all guest users
$guestUsers = Get-MgUser -Filter "userType eq 'Guest'" -All
# Process each guest user
foreach ($guest in $guestUsers) {
# Get the creation date and last sign-in time
$creationDate = $guest.CreatedDateTime
$signInActivity = Get-MgUser -UserId $guest.Id -Property SignInActivity | Select-Object -ExpandProperty SignInActivity
$lastSignIn = $signInActivity.LastSignInDateTime
# Calculate days since last activity
$daysSinceLastSignIn = if ($lastSignIn) {
(New-TimeSpan -Start $lastSignIn -End (Get-Date)).Days
} else {
"Never signed in"
}
# Add to the collection
$allGuestUsers += [PSCustomObject]@{
Tenant = $tenant.Name
DisplayName = $guest.DisplayName
Email = $guest.Mail
InvitedBy = $guest.InvitedBy.AdditionalProperties.user.displayName
CreatedDate = $creationDate
LastSignIn = $lastSignIn
DaysSinceLastSignIn = $daysSinceLastSignIn
}
}
# Disconnect from Microsoft Graph
Disconnect-MgGraph
}
## Export the report to CSV
$reportDate = Get-Date -Format "yyyyMMdd"
$allGuestUsers | Export-Csv -Path "GuestUserReport_$reportDate.csv" -NoTypeInformation
Write-Host "Guest user report generated successfully: GuestUserReport_$reportDate.csv" -ForegroundColor Green
This script generates a comprehensive report of all guest users across multiple tenants, including details such as when they were created, who invited them, and when they last signed in.
Best Practices for Multi-Tenant PowerShell Management
Based on our experience working with numerous enterprise clients, here are some best practices for managing multiple Microsoft 365 tenants with PowerShell:
-
Use secure credential management: Leverage the SecretManagement module or Azure Key Vault to store credentials securely.
-
Implement error handling: Always include try/catch blocks and logging in your scripts to handle errors gracefully.
-
Use throttling awareness: Microsoft’s APIs have rate limits. Implement pauses or batching in scripts that process large datasets.
-
Document your scripts: Include detailed comments and documentation for all automation scripts.
-
Test in non-production first: Always test your scripts in a test tenant before applying changes to production environments.
-
Use parameterization: Create reusable scripts with parameters rather than hardcoding values.
-
Implement logging: Maintain detailed logs of all actions performed by automation scripts for auditing purposes.
Conclusion
PowerShell offers tremendous capabilities for streamlining Microsoft 365 tenant management. By automating routine tasks, ensuring consistency across tenants, and generating comprehensive reports, IT administrators can significantly reduce the complexity and time required for multi-tenant management.
As Microsoft 365 continues to evolve, so too does the PowerShell ecosystem surrounding it. Staying updated with the latest modules and best practices will ensure your tenant management remains efficient and secure.
References
-
Microsoft. (2024). Microsoft Graph PowerShell SDK Documentation. https://docs.microsoft.com/en-us/powershell/microsoftgraph/
-
Microsoft. (2024). Microsoft 365 DSC Documentation. https://microsoft365dsc.com/
-
Redmond, T. (2023). Office 365 for IT Pros (2023 Edition). https://office365itpros.com/
-
Microsoft. (2024). SharePoint Online Management Shell Documentation. https://docs.microsoft.com/en-us/powershell/sharepoint/sharepoint-online/introduction-sharepoint-online-management-shell