← Back to Blog

Microsoft Graph PowerShell SDK: A Practical Guide (2026)

Connect, query, and automate Microsoft 365 with the Microsoft Graph PowerShell SDK — auth flows, common cmdlets, scopes, app-only access, and the gotchas.

Microsoft Graph PowerShell SDK: A Practical Guide (2026)


Why the Microsoft Graph PowerShell SDK Is Now the Default

If you still have AzureAD or MSOnline scripts in production, you are running on borrowed time. Both modules are deprecated, the underlying Azure AD Graph endpoint has been retired in stages through 2024 and 2025, and Microsoft has pushed every admin and automation scenario onto Microsoft Graph. The Microsoft Graph PowerShell SDK is the successor — a single module suite that wraps the entire Graph surface in PowerShell cmdlets and ships with first-class app-only and managed-identity authentication.

The SDK is huge, which is its main complaint. The full Microsoft.Graph umbrella is over 40 sub-modules. Cmdlet names are autogenerated and verbose. The mapping from a Graph REST endpoint to a cmdlet is not always obvious. None of that is a reason to stick with the dead modules — it is a reason to learn the SDK properly once and stop rewriting auth boilerplate.

This guide walks through the parts of the SDK you will actually use day-to-day: installing only what you need, signing in interactively or with an app, picking the right scopes, querying users and groups, paging large result sets, calling endpoints the cmdlets do not cover, and the production patterns for unattended scripts and Azure Automation.

Install Just the Sub-Modules You Need

Installing the Microsoft.Graph meta-module pulls every sub-module — about 800 MB on disk, several minutes to install, and a noticeable cold-start hit on every script run. You almost never need all of it.

# Install only what you use:
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.Identity.SignIns -Scope CurrentUser

# Verify
Get-Module Microsoft.Graph.* -ListAvailable | Select-Object Name, Version

Microsoft.Graph.Authentication is the one constant — every other sub-module depends on it. Add sub-modules as your script grows. If you eventually need the umbrella, install Microsoft.Graph once, then forget about it. The SDK targets PowerShell 7+ on Windows, macOS, and Linux; Windows PowerShell 5.1 still works but is officially the slow path.

For automation hosts, pin a version in your script so a module update on the agent does not silently change cmdlet behavior:

#Requires -Modules @{ ModuleName='Microsoft.Graph.Authentication'; ModuleVersion='2.30.0' }
#Requires -Modules @{ ModuleName='Microsoft.Graph.Users'; ModuleVersion='2.30.0' }

Connect-MgGraph: The Five Auth Flows You Care About

Connect-MgGraph is the single entry point and supports every flow Graph supports. The five you will use 99% of the time:

# 1. Interactive (delegated) — opens a browser, prompts for consent
Connect-MgGraph -Scopes 'User.Read.All','Group.Read.All'

# 2. Device code — for headless or shared shells, prints a code to enter on another device
Connect-MgGraph -Scopes 'User.Read.All' -UseDeviceCode

# 3. App-only with client secret (DEMO ONLY — see below for the production version)
$secret = ConvertTo-SecureString $env:GRAPH_CLIENT_SECRET -AsPlainText -Force
$cred = [pscredential]::new($env:GRAPH_CLIENT_ID, $secret)
Connect-MgGraph -TenantId $env:GRAPH_TENANT_ID -ClientSecretCredential $cred

# 4. App-only with certificate — the production-grade option
Connect-MgGraph -TenantId $env:GRAPH_TENANT_ID
-ClientId $env:GRAPH_CLIENT_ID

-CertificateThumbprint $env:GRAPH_CERT_THUMBPRINT

# 5. Managed Identity — for Azure-hosted scripts (Functions, Automation, VMs)
Connect-MgGraph -Identity

A few rules worth burning in.

Interactive auth uses delegated permissions and is bounded by what the signed-in user can do. App-only auth uses application permissions and is bounded by the consented app role assignments. They are different permission models — User.Read.All delegated is not the same as User.Read.All application. Mismatch is the most common reason a script "works on my laptop" and fails in production.

Client secrets are fine for prototypes and never fine for shipping. Certificates rotate cleanly, never appear in logs, and are what every Microsoft sample uses for production. Managed identities are even better when available, because there is no secret to rotate at all.

After connecting, sanity-check the context:

$ctx = Get-MgContext
$ctx | Select-Object Account, AppName, AuthType, TenantId, Scopes

AuthType will be Delegated, AppOnly, or ManagedIdentity. Scopes shows what was actually granted, which often differs from what you asked for — Entra trims scopes that the app or user is not entitled to, silently.

Picking the Right Scopes

Over-scoping is how interactive scripts end up needing tenant-admin consent forever. Ask only for the least-privileged scope that satisfies the call you make. Microsoft publishes the required scopes for every Graph endpoint in its reference docs; the SDK also ships Find-MgGraphCommand for working backwards from a cmdlet to its required permissions:

Find-MgGraphCommand -Command Get-MgUser | Select-Object -First 1 -ExpandProperty Permissions

That returns the permission name, type (delegated vs application), and consent description. Useful when you are debugging "Insufficient privileges" in app-only mode and cannot tell whether you need User.Read.All or Directory.Read.All.

When you are designing an unattended app, prefer the most specific resource scope:

GoalBad scopeBetter scope
Read all usersDirectory.Read.AllUser.Read.All
Read a single SharePoint siteSites.Read.AllSites.Selected (per-site role assignment)
Send mail as a service accountMail.Send (delegated)Mail.Send (application, scoped via application access policy)
Read group membershipGroup.Read.AllGroupMember.Read.All

For the Sites.Selected pattern in particular, see the deep dive in Microsoft Graph Sites.Selected: Least-Privilege SharePoint App Permissions.

Common Read Patterns: Users, Groups, Sign-Ins

Once connected, the SDK feels like normal PowerShell. The cmdlet naming follows -Mg and matches the Graph noun structure.

# A single user
Get-MgUser -UserId 'jane.doe@contoso.com' |
Select-Object Id, DisplayName, Mail, AccountEnabled

# All members of a group, paged
$group = Get-MgGroup -Filter "displayName eq 'Engineering'" -Top 1
Get-MgGroupMember -GroupId $group.Id -All |
ForEach-Object { $_.AdditionalProperties['userPrincipalName'] }

# Sign-in logs for the last 24 hours
$since = (Get-Date).AddDays(-1).ToString('o')
Get-MgAuditLogSignIn -Filter "createdDateTime ge $since" -Top 50 |
Select-Object UserPrincipalName, AppDisplayName, Status, IpAddress

Three things trip people up here.

-All means "page through everything" — without it, you get one page (default 100 items). Forgetting -All is the classic reason a script reports 100 users in a 5,000-user tenant and nobody notices for a week.

Get-MgGroupMember returns the polymorphic microsoftGraphDirectoryObject shape. The properties you actually want — userPrincipalName, mail, displayName — are stuffed into AdditionalProperties. Always inspect with Format-List * once before you build a pipeline against it.

Filters use OData v4 syntax, not PowerShell. eq, and, startswith, contains — and the property names are camelCase Graph names (displayName), not the cmdlet's PascalCase parameter names (DisplayName). When in doubt, mirror the Graph filter docs exactly.

Beyond Cmdlets: Invoke-MgGraphRequest

Cmdlet coverage is good but not complete. New beta endpoints, niche workloads (compliance, security, education), and any preview API may not have a cmdlet wrapper. The escape hatch is Invoke-MgGraphRequest, which runs a raw Graph call using the same auth context the SDK already established:

# Beta endpoint with no dedicated cmdlet (chat messages in a Team)
$teamId = 'b1b2b3b4-...'
$channelId = '19:abc...@thread.tacv2'
$uri = "https://graph.microsoft.com/beta/teams/$teamId/channels/$channelId/messages"

$resp = Invoke-MgGraphRequest -Method GET -Uri $uri -OutputType PSObject
$resp.value | Select-Object id, @{n='from'; e={$_.from.user.displayName}}, body

Use -OutputType PSObject to get strongly typed results; the default returns a hashtable, which is useful for serialization but ugly to pipe. For POST/PATCH calls, pass -Body @{ ... } and -ContentType application/json. The SDK takes care of serialization and the bearer token.

Because Invoke-MgGraphRequest shares the bearer token with the rest of the SDK, throttling counters and Retry-After semantics still apply. If you build a script that fires hundreds of these in a tight loop, wrap them in the same retry-with-backoff layer described in Microsoft Graph Throttling: Survive 429s with Smart Retry, Backoff, and Caching.

Production Patterns: Azure Automation and Functions

The SDK becomes a different beast when you move it off your laptop. Two patterns dominate.

Azure Automation runbook with system-assigned managed identity. Enable the system-assigned identity on the Automation account, grant it the Graph application permissions you need (via Microsoft Graph Command Line Tools or New-MgServicePrincipalAppRoleAssignment), import only the sub-modules your runbook calls, and connect with -Identity:

Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Users

Connect-MgGraph -Identity -NoWelcome

# Disable users with no sign-in in 90 days
$cutoff = (Get-Date).AddDays(-90).ToString('o')
$stale = Get-MgUser -All -Property Id,DisplayName,SignInActivity
-Filter "signInActivity/lastSignInDateTime le $cutoff and accountEnabled eq true"

foreach ($u in $stale) {
Update-MgUser -UserId $u.Id -AccountEnabled:$false
Write-Output "Disabled $($u.DisplayName)"
}

-NoWelcome suppresses the chatty banner that otherwise spams runbook output. Always include it in unattended scripts.

Azure Functions PowerShell with requirements.psd1. Pin the SDK there:

@{
'Microsoft.Graph.Authentication' = '2.*'
'Microsoft.Graph.Users' = '2.*'
}

In profile.ps1, do not call Connect-MgGraph. Cold starts are slow, and a connect on every invocation will throttle in volume. Instead, call it lazily inside the function and let the SDK cache the token. For Functions Premium and dedicated plans, use a system-assigned managed identity exactly like the Automation example.

If you are doing batch reads against thousands of users or messages, also look at the JSON Batch API — it lets you ship up to 20 sub-requests per HTTP round trip. The companion guide Microsoft Graph $batch Requests shows the cmdlet-free approach with Invoke-MgGraphRequest.

Common Pitfalls

A few things bite almost everyone the first time.

Disconnect-MgGraph does not always clear cached tokens. If you swap from delegated to app-only mid-session, you may keep getting the old context until you restart the host. When in doubt, Disconnect-MgGraph; Get-MgContext should return null before you reconnect.

The cmdlet's -Property parameter is the projection ($select). Without it, the SDK requests every default property, which inflates payloads and counts heavier against throttling limits. Always specify the columns you need.

Update-Mg* cmdlets do PATCH, not PUT. Passing a property as $null is interpreted as "no change", not "clear it". To explicitly clear a value, use Invoke-MgGraphRequest -Method PATCH with a JSON body and an explicit null.

The SDK targets v1.0 Graph by default. Select-MgProfile -Name beta was deprecated in 2.0; the new way is -MgGraphRequestVersion beta on a per-call basis, or build the URL yourself with Invoke-MgGraphRequest -Uri https://graph.microsoft.com/beta/.... Avoid sticking to beta for production paths — beta endpoints break without notice.

Get-MgContext returning Scopes: User.Read after you asked for User.Read.All is the SDK telling you that consent was downgraded — almost always because the app registration was missing the admin-consented permission. Fix the app registration, do not retry the script.

Wrapping Up

The Microsoft Graph PowerShell SDK is a sprawling but capable replacement for the legacy modules. The cheat sheet that gets you 90% of the way: install only the sub-modules you use, prefer certificates or managed identity over secrets, ask for the least scope that works, remember -All and -Property, and reach for Invoke-MgGraphRequest` when the cmdlets fall short.

If you are starting fresh, pair this with the foundational Microsoft Graph API authentication guide and the Getting started with Microsoft Graph API walkthrough. Both cover the OAuth and app-registration ground that the SDK papers over but does not eliminate.

Once your scripts are running clean, the next chapter is governance — managing the apps you registered, rotating their credentials, and keeping their permission grants honest. That is a separate problem, and one your future self will thank you for solving early.

Free Developer Tool

GUID Generator

Generate cryptographically secure GUIDs for SPFx manifests, Azure AD registrations, and Power Platform solutions — free and instant.

Try It Free →