Practical Microsoft 365 administration scripts for IT Directors, Microsoft 365 administrators, and organizations managing M365 environments. Built by 4th and Bailey — a Microsoft Cloud Solution Provider (CSP) headquartered in Houston, TX.
Scripts cover MFA compliance reporting, inactive user detection, license optimization, mailbox statistics, and group membership auditing — all via Microsoft Graph API v1.0 with no beta endpoints.
4TH AND BAILEY | Information Technology Consulting (4thandbailey.com) — Where IT Works
4th and Bailey is a boutique Enterprise IT Consulting firm headquartered in Houston, TX, specializing in Microsoft Cloud infrastructure, cybersecurity, and infrastructure governance. We are a Microsoft Cloud Solution Provider (CSP) serving organizations nationwide.
These tools are the freeware subset of our Microsoft Cloud Practice Toolkit — practical, production-tested scripts we use daily that we believe the broader IT community will find useful.
Every script is:
- Cross-platform — PowerShell 7.0+ on Windows, macOS, and Linux
- Graph API v1.0 only — no beta endpoints in production scripts
- Read-only — no write, modify, or delete operations
- HTML report ready — branded, client-deliverable output via
-HtmlReport - App-only and delegated auth — supports both interactive and unattended execution
Every Microsoft 365 engagement begins with understanding the current state of the tenant. Before we can recommend anything — whether that is a licensing optimization, a security posture improvement, or a governance framework — we need accurate data.
These scripts answer the questions we ask in every engagement:
- Who has MFA enabled — and who does not? (
Get-MFAStatusReport) - Which licensed users have not signed in for 60 or 90 days? (
Get-InactiveUserReport) - How many license seats are unused or misallocated? (
Get-LicenseAssignmentReport) - How large are mailboxes, and who has not been active? (
Get-MailboxStatisticsReport) - What do group memberships actually look like across the tenant? (
Get-GroupMembershipReport)
We use these tools in our Microsoft 365 assessments, infrastructure governance reviews, and client onboarding. We open-sourced them because good tools should be shared.
| Script | Description | Key Permissions |
|---|---|---|
Get-MailboxStatisticsReport.ps1 |
Mailbox size, item count, last activity | Reports.Read.All |
Get-LicenseAssignmentReport.ps1 |
Per-user license assignments + tenant SKU inventory | User.Read.All |
Get-InactiveUserReport.ps1 |
Users with no sign-in activity (30/60/90 days) | User.Read.All, AuditLog.Read.All |
Get-GroupMembershipReport.ps1 |
All groups and members — flat CSV + grouped HTML | Group.Read.All |
Get-MFAStatusReport.ps1 |
Per-user MFA status and registered auth methods | UserAuthenticationMethod.Read.All |
# macOS
brew install powershell
# Linux (Ubuntu/Debian)
sudo snap install powershell --classic
# Windows
winget install Microsoft.PowerShell# Install all required modules
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
Install-Module Microsoft.Graph.Reports -Scope CurrentUser
Install-Module Microsoft.Graph.Users -Scope CurrentUser
Install-Module Microsoft.Graph.Groups -Scope CurrentUser
Install-Module Microsoft.Graph.Identity.SignIns -Scope CurrentUserAll scripts support two authentication modes:
# No parameters required — browser prompt will open
.\Get-MailboxStatisticsReport.ps1 -HtmlReportCreate an App Registration in Entra ID with the required permissions for each script, then:
# Windows — certificate thumbprint
.\Get-MailboxStatisticsReport.ps1 `
-TenantId "your-tenant-id" `
-ClientId "your-client-id" `
-CertificateThumbprint "your-cert-thumbprint" `
-HtmlReport
# macOS / Linux — certificate file
.\Get-MailboxStatisticsReport.ps1 `
-TenantId "your-tenant-id" `
-ClientId "your-client-id" `
-CertificatePath "/path/to/cert.pfx" `
-CertificatePassword (Read-Host -AsSecureString "Certificate Password") `
-HtmlReportGenerates a mailbox statistics report using the Graph Reports API. Shows storage usage, item count, deleted item count, and last activity date for every mailbox. Sorted by storage descending — largest mailboxes first.
Required permissions: Reports.Read.All, User.Read.All
# Interactive, HTML report, last 30 days (default)
.\Get-MailboxStatisticsReport.ps1 -HtmlReport
# Last 90 days, save to specific directory
.\Get-MailboxStatisticsReport.ps1 -Period 90 -OutputPath ~/Reports -HtmlReportOutput:
MailboxStatistics_YYYYMMDD_HHmmss.csvMailboxStatistics_YYYYMMDD_HHmmss.html(with-HtmlReport)
Per-user license assignment report with tenant SKU inventory. Shows every user's assigned licenses, account status, and last sign-in. The SKU inventory table shows total, used, and available seats across all subscribed plans — useful for identifying over-licensed or under-utilized SKUs.
Required permissions: User.Read.All, Organization.Read.All
# Interactive, with HTML report
.\Get-LicenseAssignmentReport.ps1 -HtmlReport
# Include SKU part numbers in CSV
.\Get-LicenseAssignmentReport.ps1 -HtmlReport -IncludeServicePlansOutput:
LicenseAssignment_YYYYMMDD_HHmmss.csvLicenseAssignment_YYYYMMDD_HHmmss.html(with-HtmlReport)
Identifies users with no sign-in activity beyond a configurable threshold (30, 60, or 90 days). Highlights licensed accounts that are inactive — the most common source of M365 license waste. Requires Entra ID P1 or P2 for signInActivity data.
Required permissions: User.Read.All, AuditLog.Read.All
# 90-day threshold, exclude guests and disabled, HTML report
.\Get-InactiveUserReport.ps1 -InactiveDays 90 -ExcludeGuests -ExcludeDisabled -HtmlReport
# 60-day threshold, all user types
.\Get-InactiveUserReport.ps1 -InactiveDays 60 -HtmlReportOutput:
InactiveUsers_90d_YYYYMMDD_HHmmss.csvInactiveUsers_90d_YYYYMMDD_HHmmss.html(with-HtmlReport)
Exports all groups and their members to a flat CSV where each row is a group-member pair. The HTML report renders each group as a card with its members in a table — significantly more readable than a flat list for presentation to clients or leadership.
Required permissions: Group.Read.All, GroupMember.Read.All, User.Read.All
# All group types, HTML report
.\Get-GroupMembershipReport.ps1 -HtmlReport
# Security groups only, exclude empty groups
.\Get-GroupMembershipReport.ps1 -GroupType Security -ExcludeEmptyGroups -HtmlReport
# Microsoft 365 groups only
.\Get-GroupMembershipReport.ps1 -GroupType M365 -HtmlReportOutput:
GroupMembership_YYYYMMDD_HHmmss.csvGroupMembership_YYYYMMDD_HHmmss.html(with-HtmlReport)
Per-user MFA status and registered authentication methods. Classifies users as:
| Status | Meaning |
|---|---|
No MFA |
No MFA methods registered — immediate risk |
Legacy MFA Only |
Phone/SMS only — no Authenticator App or FIDO2 |
MFA Registered |
MFA registered but type unclear |
Strong MFA |
Microsoft Authenticator App or FIDO2/Passkey |
Report is sorted No MFA → Legacy → Registered → Strong for immediate action prioritization.
Required permissions: User.Read.All, UserAuthenticationMethod.Read.All
Note:
UserAuthenticationMethod.Read.Allrequires Global Admin or Authentication Administrator consent.
# Licensed users only, exclude guests, HTML report
.\Get-MFAStatusReport.ps1 -LicensedUsersOnly -ExcludeGuests -HtmlReport
# All users
.\Get-MFAStatusReport.ps1 -HtmlReportOutput:
MFAStatus_YYYYMMDD_HHmmss.csvMFAStatus_YYYYMMDD_HHmmss.html(with-HtmlReport)
All scripts produce:
- CSV — machine-readable, importable into Excel, Power BI, or any reporting tool
- HTML (optional,
-HtmlReport) — branded, client-ready report with summary metrics
HTML reports use the 4th and Bailey design system: Segoe UI typography, Communication Blue (#0C447C) brand color, and a consistent card-based layout.
signInActivity returns null for all users
Entra ID P1 or P2 is required in the tenant for sign-in activity data. Without it, LastSignInDateTime will be null and all users will appear as inactive.
UserAuthenticationMethod.Read.All permission denied
This permission requires admin consent. In delegated mode, the signed-in user must hold Global Admin or Authentication Administrator. In app-only mode, grant admin consent in the Azure Portal under App Registrations → API Permissions.
Rate limiting on large tenants Graph API enforces throttling on high-volume requests. Scripts include basic error handling. For tenants with 10,000+ users, consider running during off-peak hours.
Module not found
Run Install-Module <module-name> -Scope CurrentUser for each required module listed in the script header.
- All scripts are read-only. No write, modify, or delete Graph API calls are made.
- No data is transmitted to any third party. All output is written locally.
- Credentials are handled by the Microsoft Graph PowerShell SDK and never stored by these scripts.
- For app-only authentication, use certificate-based auth (not client secrets) in production.
Issues and pull requests are welcome. Please open an issue before submitting a PR for significant changes.
MIT License — free to use, modify, and distribute. See LICENSE for details.
4TH AND BAILEY | Information Technology Consulting Enterprise IT Consulting · Microsoft CSP · Houston, TX · Nationwide
- Microsoft Solutions
- Start a Conversation
- Website
- GitHub
- (888) 305-5977
Boutique firm. Enterprise standards. Principal-led on every engagement.
