Microsoft 365 Tenant Manager¶
Domain: Engineering - Core | Skill: ms365-tenant-manager | Source: engineering-team/ms365-tenant-manager/SKILL.md
Microsoft 365 Tenant Manager¶
Expert guidance and automation for Microsoft 365 Global Administrators managing tenant setup, user lifecycle, security policies, and organizational optimization.
Quick Start¶
Run a Security Audit¶
Connect-MgGraph -Scopes "Directory.Read.All","Policy.Read.All","AuditLog.Read.All"
Get-MgSubscribedSku | Select-Object SkuPartNumber, ConsumedUnits, @{N="Total";E={$_.PrepaidUnits.Enabled}}
Get-MgPolicyAuthorizationPolicy | Select-Object AllowInvitesFrom, DefaultUserRolePermissions
Bulk Provision Users from CSV¶
# CSV columns: DisplayName, UserPrincipalName, Department, LicenseSku
Import-Csv .\new_users.csv | ForEach-Object {
$passwordProfile = @{ Password = (New-Guid).ToString().Substring(0,16) + "!"; ForceChangePasswordNextSignIn = $true }
New-MgUser -DisplayName $_.DisplayName -UserPrincipalName $_.UserPrincipalName `
-Department $_.Department -AccountEnabled -PasswordProfile $passwordProfile
}
Create a Conditional Access Policy (MFA for Admins)¶
$adminRoles = (Get-MgDirectoryRole | Where-Object { $_.DisplayName -match "Admin" }).Id
$policy = @{
DisplayName = "Require MFA for Admins"
State = "enabledForReportingButNotEnforced" # Start in report-only mode
Conditions = @{ Users = @{ IncludeRoles = $adminRoles } }
GrantControls = @{ Operator = "OR"; BuiltInControls = @("mfa") }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
Workflows¶
Workflow 1: New Tenant Setup¶
Step 1: Generate Setup Checklist
Confirm prerequisites before provisioning: - Global Admin account created and secured with MFA - Custom domain purchased and accessible for DNS edits - License SKUs confirmed (E3 vs E5 feature requirements noted)
Step 2: Configure and Verify DNS Records
# After adding the domain in the M365 admin center, verify propagation before proceeding
$domain = "company.com"
Resolve-DnsName -Name "_msdcs.$domain" -Type NS -ErrorAction SilentlyContinue
# Also run from a shell prompt:
# nslookup -type=MX company.com
# nslookup -type=TXT company.com # confirm SPF record
Wait for DNS propagation (up to 48 h) before bulk user creation.
Step 3: Apply Security Baseline
# Disable legacy authentication (blocks Basic Auth protocols)
$policy = @{
DisplayName = "Block Legacy Authentication"
State = "enabled"
Conditions = @{ ClientAppTypes = @("exchangeActiveSync","other") }
GrantControls = @{ Operator = "OR"; BuiltInControls = @("block") }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
# Enable unified audit log
Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true
Step 4: Provision Users
$licenseSku = (Get-MgSubscribedSku | Where-Object { $_.SkuPartNumber -eq "ENTERPRISEPACK" }).SkuId
Import-Csv .\employees.csv | ForEach-Object {
try {
$user = New-MgUser -DisplayName $_.DisplayName -UserPrincipalName $_.UserPrincipalName `
-AccountEnabled -PasswordProfile @{ Password = (New-Guid).ToString().Substring(0,12)+"!"; ForceChangePasswordNextSignIn = $true }
Set-MgUserLicense -UserId $user.Id -AddLicenses @(@{ SkuId = $licenseSku }) -RemoveLicenses @()
Write-Host "Provisioned: $($_.UserPrincipalName)"
} catch {
Write-Warning "Failed $($_.UserPrincipalName): $_"
}
}
Validation: Spot-check 3–5 accounts in the M365 admin portal; confirm licenses show "Active."
Workflow 2: Security Hardening¶
Step 1: Run Security Audit
Connect-MgGraph -Scopes "Directory.Read.All","Policy.Read.All","AuditLog.Read.All","Reports.Read.All"
# Export Conditional Access policy inventory
Get-MgIdentityConditionalAccessPolicy | Select-Object DisplayName, State |
Export-Csv .\ca_policies.csv -NoTypeInformation
# Find accounts without MFA registered
$report = Get-MgReportAuthenticationMethodUserRegistrationDetail
$report | Where-Object { -not $_.IsMfaRegistered } |
Select-Object UserPrincipalName, IsMfaRegistered |
Export-Csv .\no_mfa_users.csv -NoTypeInformation
Write-Host "Audit complete. Review ca_policies.csv and no_mfa_users.csv."
Step 2: Create MFA Policy (report-only first)
$policy = @{
DisplayName = "Require MFA All Users"
State = "enabledForReportingButNotEnforced"
Conditions = @{ Users = @{ IncludeUsers = @("All") } }
GrantControls = @{ Operator = "OR"; BuiltInControls = @("mfa") }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
Validation: After 48 h, review Sign-in logs in Entra ID; confirm expected users would be challenged, then change State to "enabled".
Step 3: Review Secure Score
# Retrieve current Secure Score and top improvement actions
Get-MgSecuritySecureScore -Top 1 | Select-Object CurrentScore, MaxScore, ActiveUserCount
Get-MgSecuritySecureScoreControlProfile | Sort-Object -Property ActionType |
Select-Object Title, ImplementationStatus, MaxScore | Format-Table -AutoSize
Workflow 3: User Offboarding¶
Step 1: Block Sign-in and Revoke Sessions
$upn = "departing.user@company.com"
$user = Get-MgUser -Filter "userPrincipalName eq '$upn'"
# Block sign-in immediately
Update-MgUser -UserId $user.Id -AccountEnabled:$false
# Revoke all active tokens
Invoke-MgInvalidateAllUserRefreshToken -UserId $user.Id
Write-Host "Sign-in blocked and sessions revoked for $upn"
Step 2: Preview with -WhatIf (license removal)
# Identify assigned licenses
$licenses = (Get-MgUserLicenseDetail -UserId $user.Id).SkuId
# Dry-run: print what would be removed
$licenses | ForEach-Object { Write-Host "[WhatIf] Would remove SKU: $_" }
Step 3: Execute Offboarding
# Remove licenses
Set-MgUserLicense -UserId $user.Id -AddLicenses @() -RemoveLicenses $licenses
# Convert mailbox to shared (requires ExchangeOnlineManagement module)
Set-Mailbox -Identity $upn -Type Shared
# Remove from all groups
Get-MgUserMemberOf -UserId $user.Id | ForEach-Object {
try { Remove-MgGroupMemberByRef -GroupId $_.Id -DirectoryObjectId $user.Id } catch {}
}
Write-Host "Offboarding complete for $upn"
Validation: Confirm in the M365 admin portal that the account shows "Blocked," has no active licenses, and the mailbox type is "Shared."
Best Practices¶
Tenant Setup¶
- Enable MFA before adding users
- Configure named locations for Conditional Access
- Use separate admin accounts with PIM
- Verify custom domains (and DNS propagation) before bulk user creation
- Apply Microsoft Secure Score recommendations
Security Operations¶
- Start Conditional Access policies in report-only mode
- Review Sign-in logs for 48 h before enforcing a new policy
- Never hardcode credentials in scripts — use Azure Key Vault or
Get-Credential - Enable unified audit logging for all operations
- Conduct quarterly security reviews and Secure Score check-ins
PowerShell Automation¶
- Prefer Microsoft Graph (
Microsoft.Graphmodule) over legacy MSOnline - Include
try/catchblocks for error handling - Implement
Write-Host/Write-Warninglogging for audit trails - Use
-WhatIfor dry-run output before bulk destructive operations - Test in a non-production tenant first
Reference Guides¶
references/powershell-templates.md - Ready-to-use script templates - Conditional Access policy examples - Bulk user provisioning scripts - Security audit scripts
references/security-policies.md - Conditional Access configuration - MFA enforcement strategies - DLP and retention policies - Security baseline settings
references/troubleshooting.md - Common error resolutions - PowerShell module issues - Permission troubleshooting - DNS propagation problems
Limitations¶
| Constraint | Impact |
|---|---|
| Global Admin required | Full tenant setup needs highest privilege |
| API rate limits | Bulk operations may be throttled |
| License dependencies | E3/E5 required for advanced features |
| Hybrid scenarios | On-premises AD needs additional configuration |
| PowerShell prerequisites | Microsoft.Graph module required |
Required PowerShell Modules¶
Install-Module Microsoft.Graph -Scope CurrentUser
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Install-Module MicrosoftTeams -Scope CurrentUser
Required Permissions¶
- Global Administrator — Full tenant setup
- User Administrator — User management
- Security Administrator — Security policies
- Exchange Administrator — Mailbox management