Microsoft Graph is the unified REST API for the Microsoft 365 / Entra / Intune universe. It replaced a dozen service-specific APIs. The Microsoft Graph PowerShell SDK wraps it in cmdlets — and supersedes a long list of older PowerShell modules:
| Legacy module | Replaced by |
|---|---|
MSOnline (Connect-MsolService) | Microsoft.Graph.* |
| AzureAD / AzureADPreview | Microsoft.Graph.* |
| Some Exchange Online cmdlets | Microsoft.Graph.* |
| Intune PowerShell SDK (older) | Microsoft.Graph.DeviceManagement |
Migrate from MSOnline / AzureAD — Microsoft has been deprecating them since 2024.
Installation
# Install just what you need — the full meta-module is huge.
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
Install-Module Microsoft.Graph.Users -Scope CurrentUser
Install-Module Microsoft.Graph.Groups -Scope CurrentUser
Install-Module Microsoft.Graph.DirectoryObjects -Scope CurrentUser
# Or the lot (large download):
Install-Module Microsoft.Graph -Scope CurrentUser
Connecting
Interactive (with scopes)
Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All", "Directory.Read.All"
Get-MgContext
Disconnect-MgGraph
Scopes are Microsoft Graph permission strings. Browse them in the Graph Explorer — also the best place to test API calls before scripting them.
App-only with certificate (production)
# Register an app in Entra ID, grant it application permissions (not delegated), upload a certificate.
Connect-MgGraph -ClientId $clientId -TenantId $tenantId -CertificateThumbprint $thumbprint
App-only auth uses application permissions — broader scope, no signed-in user. Use a Key Vault-stored certificate, not a client secret.
Managed Identity
Connect-MgGraph -Identity
The MI must have Graph application permissions granted. Best choice from Azure Functions, Azure Automation, VMs.
Working with Users
# List
Get-MgUser -All | Select-Object DisplayName, UserPrincipalName, Department
# Filter (Graph $filter syntax — different from Where-Object)
Get-MgUser -Filter "department eq 'Engineering'"
Get-MgUser -Filter "startsWith(displayName,'Alice')"
# Get one
$user = Get-MgUser -UserId alice@acme.io
$user | Get-Member
# Update
Update-MgUser -UserId alice@acme.io -Department "Platform"
# Create
$password = @{ ForceChangePasswordNextSignIn = $true; Password = "TempPa55w0rd!" }
New-MgUser -DisplayName "Bob Smith" -UserPrincipalName bob@acme.io `
-AccountEnabled -MailNickname bob -PasswordProfile $password
# Disable
Update-MgUser -UserId bob@acme.io -AccountEnabled:$false
Groups and Membership
# Create a security group
$group = New-MgGroup -DisplayName "Engineering" -MailNickname engineering `
-SecurityEnabled -MailEnabled:$false
# Add members
$user = Get-MgUser -UserId alice@acme.io
New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $user.Id
# List members
Get-MgGroupMember -GroupId $group.Id -All | ForEach-Object {
Get-MgUser -UserId $_.Id
}
# Bulk import from CSV
Import-Csv ./new-hires.csv | ForEach-Object {
$u = Get-MgUser -UserId $_.email
New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $u.Id
}
Conditional Access Policies
Install-Module Microsoft.Graph.Identity.SignIns -Scope CurrentUser
Get-MgIdentityConditionalAccessPolicy | Where-Object State -eq enabled |
Select-Object DisplayName, State, CreatedDateTime |
Sort-Object DisplayName
Conditional Access policies are full-blown objects with nested conditions and grants — easy to audit, deploy, or version-control as JSON.
Devices and Intune
Install-Module Microsoft.Graph.DeviceManagement -Scope CurrentUser
# Inventory
Get-MgDeviceManagementManagedDevice -All |
Select-Object DeviceName, OperatingSystem, ComplianceState, UserPrincipalName |
Where-Object ComplianceState -ne "compliant" |
Export-Csv ./non-compliant-devices.csv -NoTypeInformation
# Trigger sync
$device = Get-MgDeviceManagementManagedDevice -Filter "deviceName eq 'JANE-LAPTOP'"
Sync-MgDeviceManagementManagedDevice -ManagedDeviceId $device.Id
The Generic Invoke-MgGraphRequest
When no cmdlet exists, call the API directly:
$res = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/me/messages?$top=5"
$res.value | Select-Object subject, receivedDateTime
# POST with body
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/groups" -Body @{
displayName = "DevOps"
mailNickname = "devops"
securityEnabled = $true
mailEnabled = $false
} -ContentType "application/json"
Useful for beta endpoints (v1.0 → beta in URL) and for endpoints not yet covered by a cmdlet.
Exchange Online and SharePoint
The Exchange Online and SharePoint estates have their own remote PowerShell modules with Graph-style features:
ExchangeOnlineManagement— mailbox management, message trace, transport rules.Connect-ExchangeOnline.PnP.PowerShell— SharePoint, Teams, lists.Connect-PnPOnline. Far richer than Graph for SharePoint operations.MicrosoftTeams— Teams management.
Long-term direction: more of this moves to Graph each year. For now, use the dedicated modules where they have better coverage.
Practical Patterns
Audit report — inactive users
Connect-MgGraph -Scopes "AuditLog.Read.All", "User.Read.All"
$thresholdDays = 90
$inactive = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, SignInActivity |
Where-Object {
$signin = $_.SignInActivity.LastSignInDateTime
-not $signin -or ((New-TimeSpan -Start $signin -End (Get-Date)).Days -gt $thresholdDays)
} |
Select-Object DisplayName, UserPrincipalName, @{
Name="LastSignIn"
Expression={ if ($_.SignInActivity.LastSignInDateTime) { $_.SignInActivity.LastSignInDateTime } else { "never" } }
}
$inactive | Export-Csv ./inactive-users.csv -NoTypeInformation
Bulk MFA enforcement
$policy = Get-MgPolicyAuthenticationMethodPolicy
# (most MFA enforcement is policy-driven via Conditional Access now)
What to Watch Out For
- Throttling. Graph throttles aggressively at ~1000 req/min per app. Handle 429s with
Retry-After. - $filter vs Where-Object. Filter at the API (
-Filter) — far faster than fetching all and filtering locally. - Permissions sprawl. Grant the minimum scopes; app permissions especially are global.
- Module size. The meta-module loads slowly. Use sub-modules.
- beta vs v1.0. Cmdlets use v1.0 by default. Use
Select-MgProfile beta(older API) or-Uriwith/beta/for preview features.
Learning Resources
- Graph Explorer — try every endpoint, see scopes required
- Microsoft Graph PowerShell docs
- Migration guide from AzureAD / MSOnline to Graph PowerShell
Az + Graph PowerShell together cover essentially every M365 / Azure automation need. The last two lessons cover how to actually run these scripts — Automation Accounts, Functions, CI — and how to write them cross-platform.