5 PowerShell Snippets for Everyday Microsoft 365 Admin Work
These are the five queries I need most often in Microsoft 365 tenants. All of them only read, none of them change anything. They are written so you can copy them without edits.
Preparation
The full Microsoft.Graph module is huge and takes forever to install. Three submodules are enough for these snippets:
Install-Module Microsoft.Graph.Users, Microsoft.Graph.Reports, Microsoft.Graph.Identity.SignIns -Scope CurrentUser
Connect-MgGraph -Scopes 'User.Read.All','AuditLog.Read.All','UserAuthenticationMethod.Read.All'
On first sign-in Entra asks for consent to these permissions. Depending on tenant settings, an admin has to approve it.
1. Who hasn’t signed in for 90 days?
$cutoff = (Get-Date).AddDays(-90)
Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled,SignInActivity |
Where-Object {
$_.AccountEnabled -and
$_.SignInActivity.LastSignInDateTime -and
$_.SignInActivity.LastSignInDateTime -lt $cutoff
} |
Select-Object DisplayName, UserPrincipalName,
@{N='LastSignIn';E={$_.SignInActivity.LastSignInDateTime}} |
Sort-Object LastSignIn
This is my “do we still need this license?” list. Two catches: SignInActivity requires Entra ID P1. And accounts that have never signed in are deliberately left out because they have no date. I check those separately; they are often forgotten test accounts.
2. Enabled users without a license
Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled,AssignedLicenses |
Where-Object { $_.AccountEnabled -and $_.AssignedLicenses.Count -eq 0 } |
Select-Object DisplayName, UserPrincipalName
This catches accounts where someone forgot the license, plus service accounts. Shared mailboxes show up too; they usually don’t need a license, but sign-in should be blocked for them. A shared mailbox appearing here as enabled is the real finding.
3. Guest accounts, newest first
Get-MgUser -All -Filter "userType eq 'Guest'" -Property DisplayName,Mail,CreatedDateTime,SignInActivity |
Select-Object DisplayName, Mail, CreatedDateTime,
@{N='LastSignIn';E={$_.SignInActivity.LastSignInDateTime}} |
Sort-Object CreatedDateTime -Descending
Guests pile up quickly through Teams. Anyone who hasn’t been around for a year gets removed after checking with the owner.
4. Mailbox sizes as CSV
Install-Module ExchangeOnlineManagement -Scope CurrentUser # once
Connect-ExchangeOnline
Get-EXOMailbox -ResultSize Unlimited |
Get-EXOMailboxStatistics |
Select-Object DisplayName, TotalItemSize, ItemCount |
Export-Csv -Path './mailbox-sizes.csv' -NoTypeInformation -Encoding utf8
The EXO cmdlets are much faster than the old Get-Mailbox/Get-MailboxStatistics when there are many mailboxes. I need this before migrations and whenever someone asks why their mailbox is full.
5. Users without an MFA method
Get-MgReportAuthenticationMethodUserRegistrationDetail -All |
Where-Object { -not $_.IsMfaRegistered } |
Select-Object UserPrincipalName, UserDisplayName, IsAdmin, MethodsRegistered
This is the same report the Entra admin center shows under Authentication methods → User registration details, just as a list. It returns all users in one query, and the IsAdmin column immediately shows whether an admin is among them. That would be the first thing to fix.
This report also requires Entra ID P1. Without P1 you have to go user by user, which takes a while in large tenants:
Get-MgUser -All -Filter 'accountEnabled eq true' -Property Id,UserPrincipalName |
ForEach-Object {
$methods = Get-MgUserAuthenticationMethod -UserId $_.Id |
Where-Object { $_.AdditionalProperties['@odata.type'] -notmatch 'passwordAuthenticationMethod$' }
if (-not $methods) { $_.UserPrincipalName }
}
Why PowerShell instead of the portal
All of this works in the portal too. With a script I get the same query every time, can send the result to the customer as CSV, and see next time what has changed.