Building Proactive PowerShell Daily Health Checks for Multi-Tenant MSPs
How to automate daily tenant hygiene checks across Exchange Online, Entra ID, and on-premises domain controllers using certificate-based authentication and secure app registrations.
Running manual checks across dozens of Microsoft 365 tenants is untenable. Technicians miss expiring certificates, orphaned privileged accounts, and failed directory synchronizations until an outage occurs.
This runbook provides our battle-tested, headless PowerShell script architecture using Certificate-Based Authentication (CBA) to query multiple tenants securely without storing client secrets in plain text.
1. Authentication Architecture: Why Client Secrets Fail in MSPs
Storing client secrets in RMM script parameters is an audit liability:
- Secrets expire quietly (typically after 1 to 2 years) and break unattended jobs without warning.
- Secrets can be extracted from RMM agent logs or local script caches.
Instead, we generate a dedicated self-signed certificate, store the private key in the Windows Certificate Store or Azure Key Vault, and upload the public .cer to the Multi-Tenant App Registration:
[ Scheduled Task / RMM Agent ]
│
▼
[ Load Thumbprint from Cert:\LocalMachine\My ]
│
▼
[ Connect-MgGraph -CertificateThumbprint $Thumbprint -TenantId $TenantId ]
│
▼
[ Execute Automated Health Check & Post Summary to Webhook ]
2. Core Tenant Health Inspection Script
Here is the modular PowerShell script checking for three critical failure modes:
- Entra Connect Sync Latency: Alerts if directory synchronization hasn’t succeeded in the last 3 hours.
- Privileged Role Creep: Enumerates active Global Administrators.
- Expiring App Secrets & Certificates: Flags credentials expiring within 14 days.
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$TenantId,
[Parameter(Mandatory = $true)]
[string]$ClientId,
[Parameter(Mandatory = $true)]
[string]$CertThumbprint
)
# Connect headlessly using Certificate-Based Auth
try {
Connect-MgGraph -ClientId $ClientId -TenantId $TenantId -CertificateThumbprint $CertThumbprint -NoWelcome
Write-Host "[+] Successfully authenticated to tenant: $TenantId" -ForegroundColor Green
} catch {
Write-Error "[-] Failed to authenticate: $_"
exit 1
}
$HealthReport = [PSCustomObject]@{
TenantId = $TenantId
CheckTimestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
GlobalAdminCount = 0
ExpiringCredentials = @()
SyncStatus = "OK"
}
# 1. Enumerate Global Administrators
$GlobalAdminRole = Get-MgDirectoryRole | Where-Object { $_.DisplayName -eq "Global Administrator" }
if ($GlobalAdminRole) {
$Admins = Get-MgDirectoryRoleMember -DirectoryRoleId $GlobalAdminRole.Id
$HealthReport.GlobalAdminCount = $Admins.Count
}
# 2. Check for App Secrets/Certs Expiring in next 14 days
$Threshold = (Get-Date).AddDays(14)
$Applications = Get-MgApplication -All
foreach ($App in $Applications) {
foreach ($Key in $App.PasswordCredentials) {
if ($Key.EndDateTime -and $Key.EndDateTime -lt $Threshold) {
$HealthReport.ExpiringCredentials += "$($App.DisplayName) (Secret expires: $($Key.EndDateTime))"
}
}
}
# Output sanitized JSON report for PSA/RMM consumption
$HealthReport | ConvertTo-Json -Depth 3
3. Recommended Execution Schedule
- Frequency: Run once daily at 05:00 local time before business operations begin.
- Notification Routing: Send warning payloads directly to your PSA (ConnectWise Manage, Autotask, HaloPSA) or secure Teams/Slack webhook channels.
- Remediation SLA: Flag any tenant having more than 5 Global Administrators for immediate review.
Get The Next Production Runbook in Your Inbox
Join 500+ MSP & SecOps engineers. Receive battle-tested configurations, 5-minute audio briefings, and critical security advisories every Tuesday.