Hanso Group

Streamlining M365 Tenant Management

Lars Natus 11 minutes read

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:

  1. Use secure credential management: Leverage the SecretManagement module or Azure Key Vault to store credentials securely.

  2. Implement error handling: Always include try/catch blocks and logging in your scripts to handle errors gracefully.

  3. Use throttling awareness: Microsoft’s APIs have rate limits. Implement pauses or batching in scripts that process large datasets.

  4. Document your scripts: Include detailed comments and documentation for all automation scripts.

  5. Test in non-production first: Always test your scripts in a test tenant before applying changes to production environments.

  6. Use parameterization: Create reusable scripts with parameters rather than hardcoding values.

  7. 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

  1. Microsoft. (2024). Microsoft Graph PowerShell SDK Documentation. https://docs.microsoft.com/en-us/powershell/microsoftgraph/

  2. Microsoft. (2024). Microsoft 365 DSC Documentation. https://microsoft365dsc.com/

  3. Redmond, T. (2023). Office 365 for IT Pros (2023 Edition). https://office365itpros.com/

  4. Microsoft. (2024). SharePoint Online Management Shell Documentation. https://docs.microsoft.com/en-us/powershell/sharepoint/sharepoint-online/introduction-sharepoint-online-management-shell

Back to all articles