🎁 New User? Get 20% off your first purchase with code NEWUSER20 Register Now →
Menu

Categories

Microsoft Graph PowerShell SDK: Getting Started in 2026

Microsoft Graph PowerShell SDK: Getting Started in 2026
Microsoft Graph PowerShell SDK guide

Microsoft retired the old AzureAD and MSOnline PowerShell modules in 2024. The replacement is the Microsoft Graph PowerShell SDK — a single API surface that covers Entra ID (Azure AD), Exchange Online, Teams, Intune, SharePoint, and most of Microsoft 365. If you have ever scripted M365 administration, you have to migrate. The good news: the SDK is well-structured, the cmdlets are predictable (Get-MgUser, New-MgGroup, Update-MgUser), and once you understand scopes and paging, the rest is muscle memory.

This guide walks through installation, the three authentication modes, your first useful queries, paging through large result sets, batching, and the specific gotchas that bite first-time users. Free PDF cheat sheet at the end.

Install the SDK

The SDK is one big metamodule (Microsoft.Graph) that pulls in 38 sub-modules. That is a lot of disk, a lot of memory at import time, and a lot of cmdlets you will never use. The recommended approach in 2026 is to install the top-level module on demand and import only the sub-modules you actually need:

# Install everything (slow but safe)
Install-Module Microsoft.Graph -Scope CurrentUser

# Or install only what you use (faster, less memory)
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.DirectoryManagement -Scope CurrentUser

For day-to-day admin work, those four sub-modules cover 80% of use cases.

Three authentication modes

The SDK supports three sign-in models. Pick the right one for the job:

1. Interactive (delegated)

Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All"
# Opens a browser, you sign in, consent appears for the requested scopes

For day-to-day admin from your workstation. The token is cached for the session.

2. App registration with client secret (application)

$tenantId = "00000000-0000-0000-0000-000000000000"
$clientId = "11111111-1111-1111-1111-111111111111"
$secret   = ConvertTo-SecureString "your-client-secret" -AsPlainText -Force
$cred     = [pscredential]::new($clientId, $secret)

Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $cred

For unattended jobs and CI/CD. Permissions are application-level, granted once by an admin in the app registration.

3. App registration with certificate (best practice for prod)

Connect-MgGraph -TenantId $tenantId -ClientId $clientId `
                 -CertificateThumbprint "ABCDEF1234..."

Same as above but with a certificate instead of a secret — much more secure because the private key never leaves the machine.

Disconnect when done:

Disconnect-MgGraph

Scopes — the most important concept

Every Graph API call requires one or more scopes (also called permissions). They come in two flavours:

  • Delegated scopes — what the signed-in user is allowed to do. Used in interactive mode.
  • Application scopes — what the app itself can do, regardless of user. Used in unattended mode.

Common ones every admin uses:

User.Read.All           Read all user profiles
User.ReadWrite.All      Create / update users
Group.Read.All          Read all groups
Group.ReadWrite.All     Create / update groups
Directory.Read.All      Read full directory (catch-all read)
Directory.ReadWrite.All Read + write the whole directory
Mail.Send               Send mail as the signed-in user
Sites.Read.All          Read SharePoint sites
DeviceManagementConfiguration.Read.All  Intune read

Use the smallest scope that does the job. Directory.ReadWrite.All is convenient and dangerous; User.Read.All is much narrower if all you need is a user list.

Inspect what is currently granted in your session:

Get-MgContext | Select-Object -ExpandProperty Scopes

Your first useful queries

# All enabled users
Get-MgUser -All -Filter "accountEnabled eq true" | Select-Object DisplayName, UserPrincipalName

# A specific user
Get-MgUser -UserId "alice@contoso.com" -Property DisplayName, Mail, JobTitle, Department

# All groups the user is a member of
Get-MgUserMemberOf -UserId "alice@contoso.com" |
    Select-Object @{n='Name'; e={$_.AdditionalProperties.displayName}}

# Create a security group
New-MgGroup -DisplayName "Project-Atlas" -MailEnabled:$false `
            -SecurityEnabled -MailNickname "project-atlas"

# Add a member to a group
$user = Get-MgUser -UserId "alice@contoso.com"
New-MgGroupMember -GroupId "" -DirectoryObjectId $user.Id

$filter, $select, $top, $orderby

OData query parameters are passed via cmdlet parameters with the same names (minus the dollar sign):

# $filter
Get-MgUser -Filter "startsWith(displayName, 'A')"
Get-MgUser -Filter "department eq 'Engineering'"
Get-MgUser -Filter "createdDateTime ge 2025-01-01T00:00:00Z" -ConsistencyLevel eventual -CountVariable c

# $select - only return specific properties (faster)
Get-MgUser -Select "id,displayName,userPrincipalName,jobTitle"

# $top - page size (max 999 for users)
Get-MgUser -Top 50

# $orderby
Get-MgUser -Filter "department eq 'Engineering'" -OrderBy "displayName"

Some advanced filters require -ConsistencyLevel eventual — that switches to a different index that supports more operators (endsWith, not, count queries). Always check the error message; it will tell you when this is needed.

Paging through large result sets

By default, Get-MgUser returns the first page (100 records). Use -All to fetch every page automatically:

$allUsers = Get-MgUser -All -Filter "accountEnabled eq true"
$allUsers.Count  # could be 50,000+

-All handles the @odata.nextLink for you. It is the only sane default for any inventory or reporting script. Without it, you silently truncate after page 1.

Batch requests

For high-throughput scenarios (thousands of users to look up), batching cuts request count by 20x:

$ids = @("alice@contoso.com", "bob@contoso.com", "carol@contoso.com")

# Build batch
$requests = $ids | ForEach-Object -Begin { $i = 0 } -Process {
    $i++
    @{
        id     = "$i"
        method = 'GET'
        url    = "/users/$_"
    }
}

$batch = @{ requests = $requests }
$response = Invoke-MgGraphRequest -Method POST -Uri "/v1.0/`$batch" -Body $batch
$response.responses | ForEach-Object { $_.body }

A single batch can contain up to 20 requests. The Graph SDK does not have a one-shot helper for this — Invoke-MgGraphRequest is your tool.

Error handling

Graph errors come back as PowerShell errors with Microsoft.Graph.Authentication-namespaced exceptions. Always wrap unattended calls:

try {
    $u = Get-MgUser -UserId $upn -ErrorAction Stop
}
catch {
    if ($_.Exception.Message -match 'Resource.*does not exist') {
        Write-Warning "User $upn not found"
    } else {
        throw
    }
}

Throttling (HTTP 429) shows up as a transient error. The SDK respects Retry-After automatically, but bursts of thousands of calls can still trip the per-app limit. Slow down or batch.

v1.0 vs beta endpoints

Many features land in /beta first. Switch globally with Select-MgProfile:

Select-MgProfile -Name beta
Get-MgUser -UserId $upn   # now hits /beta/users/{id}
Select-MgProfile -Name v1.0   # back to stable

Beta is fine for read-only reporting. Avoid it for production write operations — schemas can change without notice.

Cheat sheet

All cmdlets, scopes, and snippets above on a single PDF: Microsoft Graph PowerShell Cheat Sheet.

FAQ

Do I have to migrate from AzureAD/MSOnline?

Yes — both modules are formally retired and stopped working against new Entra ID features in 2024. The Graph SDK is the supported successor.

Why does my script work interactively but fail in CI?

Interactive mode uses delegated scopes (your user permissions). CI uses application scopes (the app registration's permissions). They are different sets and must be granted separately by an admin in the app registration's API permissions blade.

Can I use the Graph SDK on Linux/macOS?

Yes — the SDK is cross-platform and works in PowerShell 7 on any OS.

How do I avoid loading 38 sub-modules every time?

Install only the sub-modules you need (Authentication + Users + Groups covers 80% of admin scripts). Import them explicitly at the top of your script instead of importing the meta-module.

What replaces Get-MsolUser?

Get-MgUser. Property names changed slightly (camelCase instead of PascalCase in some places), but the equivalents exist.

How do I run a Graph query that has no SDK cmdlet?

Use Invoke-MgGraphRequest -Method GET -Uri "/v1.0/path/to/resource". It is the escape hatch for any endpoint not yet wrapped.

Are there breaking changes between SDK versions?

Yes. Pin the version in production (Install-Module Microsoft.Graph -RequiredVersion 2.x.x) and bump deliberately. The SDK auto-generates from the Graph metadata so cmdlet names and parameters can shift between major releases.

Related reading

Share this article:
Dargslan Editorial Team (Dargslan)
About the Author

Dargslan Editorial Team (Dargslan)

Collective of Software Developers, System Administrators, DevOps Engineers, and IT Authors

Dargslan is an independent technology publishing collective formed by experienced software developers, system administrators, and IT specialists.

The Dargslan editorial team works collaboratively to create practical, hands-on technology books focused on real-world use cases. Each publication is developed, reviewed, and...

Programming Languages Linux Administration Web Development Cybersecurity Networking

Stay Updated

Subscribe to our newsletter for the latest tutorials, tips, and exclusive offers.