๐ŸŽ New User? Get 20% off your first purchase with code NEWUSER20 ยท โšก Instant download ยท ๐Ÿ”’ Secure checkout Register Now โ†’
Menu

Categories

Microsoft Graph PowerShell SDK: A 2026 Getting-Started Guide

Microsoft Graph PowerShell SDK: A 2026 Getting-Started Guide
Microsoft Graph PowerShell SDK - Dargslan 2026

The MSOnline and AzureAD modules are end-of-life. The Microsoft Graph PowerShell SDK is the replacement, and it covers everything those two modules ever did plus most of the Exchange Online, Intune, Teams and SharePoint admin surface. The catch is that the SDK is large, the auth model is more nuanced than the old modules, and the wrong scope at sign-in produces a confusing 403 instead of a useful message.

Install the SDK (sub-modules)

The full module is enormous. Install only the sub-modules you actually use:

Install-Module Microsoft.Graph.Authentication
Install-Module Microsoft.Graph.Users
Install-Module Microsoft.Graph.Identity.SignIns
Install-Module Microsoft.Graph.Identity.DirectoryManagement
Install-Module Microsoft.Graph.DeviceManagement     # Intune

Install-Module Microsoft.Graph works but pulls 700 MB. The selective install above is < 100 MB and starts faster.

Choose your auth model

Two models, two use cases:

  • Delegated โ€” interactive admin work. Sign in as you; the SDK acts on your behalf with the consented scopes.
  • App-only โ€” unattended automation. Register an app in Entra ID, give it application permissions, sign in with a certificate or client secret. The SDK acts as the app, not as a user.

App-only is the right choice for any scheduled task. Never use a delegated sign-in with a "service" user โ€” Microsoft no longer supports that pattern with MFA enforced.

Delegated sign-in

Connect-MgGraph -Scopes 'User.Read.All','Directory.Read.All'
Get-MgUser -Top 5

The first call opens a browser, you sign in, you consent (once per scope per tenant). Subsequent calls in the same session reuse the token.

App-only with certificate

# Register the app in Entra, grant application permissions, upload the cert public key.
$cert = Get-Item Cert:\CurrentUser\My\<thumbprint>
Connect-MgGraph -ClientId '00000000-0000-0000-0000-000000000000' `
    -TenantId 'contoso.onmicrosoft.com' -Certificate $cert
Get-MgUser -Top 5

Use a certificate, not a client secret โ€” secrets get committed to git. Store the thumbprint and let the SDK find the cert in the Windows store.

Picking the right scopes

The most common mistake: requesting Directory.ReadWrite.All for a script that only reads. The fix is to read the cmdlet help and request the minimum scope. Find-MgGraphPermission is the discovery tool:

Find-MgGraphPermission -SearchString 'SignIn' -PermissionType Delegated

Your first useful queries

# Recently created users
Get-MgUser -Filter "createdDateTime ge 2026-01-01" -All

# Users without MFA registered
Get-MgUser -All -Property Id,UserPrincipalName,AuthenticationMethods |
    Where-Object { -not ($_.AuthenticationMethods | Where Type -ne 'password') }

# Sign-in failures in the last 24h
Get-MgAuditLogSignIn -Filter "status/errorCode ne 0 and createdDateTime ge 2026-01-01"

Throttling and pagination

Graph throttles aggressively. The SDK handles 429 retries with exponential backoff transparently for most cmdlets, but very large queries should still use -PageSize 999 and -All:

Get-MgUser -All -PageSize 999 -ConsistencyLevel eventual -Count uc

-ConsistencyLevel eventual + $count is required for advanced filters and for accurate totals.

Audit checklist

  1. Selective sub-modules installed (1 pt)
  2. App-only auth used for unattended scripts (1 pt)
  3. Certificate auth, not client secret (1 pt)
  4. Minimum scopes requested (1 pt)
  5. -All + -PageSize 999 on bulk queries (1 pt)

Application authentication and least-privilege scopes

Interactive sign-in is fine while you explore. Production automation needs an app registration with a client certificate, a precisely scoped set of application permissions, and a habit of reading the consent screen before clicking.

Create an app registration with certificate auth

Skip client secrets โ€” they expire awkwardly and leak in logs. A self-signed certificate (or, better, a CA-issued one) on the calling host is the standard pattern.

# Generate the cert on the calling host
$cert = New-SelfSignedCertificate `
    -Subject 'CN=graph-automation' `
    -CertStoreLocation Cert:\CurrentUser\My `
    -KeyExportPolicy NonExportable `
    -NotAfter (Get-Date).AddYears(2)

# Export the public key to upload to Entra
Export-Certificate -Cert $cert -FilePath C:\tmp\graph-app.cer

# Connect the script to the app registration
Connect-MgGraph `
    -ClientId   '00000000-0000-0000-0000-000000000000' `
    -TenantId   'contoso.onmicrosoft.com' `
    -CertificateThumbprint $cert.Thumbprint

The certificate's private key never leaves the host. Rotate by generating a new cert, uploading the public key, and removing the old one from Entra โ€” the running script picks up the new thumbprint on next start.

Pick the smallest possible permission

The Graph documentation lists at least one delegated and one application permission for every endpoint. Always pick the read-only and resource-scoped variant first. User.Read.All is rarely the right answer; User.ReadBasic.All usually is. Audit the permissions on every app monthly with:

Get-MgServicePrincipal -Filter "appId eq '<your-app-id>'" |
    Select-Object DisplayName,
                  @{N='AppRoles';E={ $_.AppRoles.Value -join ', ' }},
                  @{N='OAuth2';  E={ $_.Oauth2PermissionScopes.Value -join ', ' }}

Handle paging properly

Almost every list endpoint pages at 100 items. The SDK exposes -All to follow @odata.nextLink automatically โ€” use it, but be aware that on a 100,000-user tenant a naive -All can run for minutes. Filter server-side first.

# Bad: pulls every user, then filters in PowerShell
Get-MgUser -All | Where-Object Department -eq 'Finance'

# Good: filter on the server
Get-MgUser -Filter "department eq 'Finance'" -ConsistencyLevel eventual -CountVariable c -All

Common pitfalls

  • Using delegated permissions for unattended scripts. Delegated tokens expire and require a user. Production automation always uses application permissions.
  • Forgetting to grant admin consent. A permission listed in the app registration is not active until a Global Admin consents. Bake the consent step into the deployment runbook.
  • Hard-coding the tenant id in scripts. Use a config file or environment variable. Multi-tenant operators get burned by this constantly.
  • Calling Connect-MgGraph in a loop. Each connect creates a new token. Authenticate once, reuse the session, disconnect at the end.
  • Ignoring throttling. Graph throttles on a sliding window and returns HTTP 429 with a Retry-After. The SDK does not retry by default โ€” wrap your loop with try/catch and honour the header.

Production scaffolding: the script template you should copy

Every Graph automation script in your repo should follow the same shape: parameter validation, certificate-based connect, scoped query, structured output, error trapping, and disconnect. Copy the skeleton below and fill in the middle.

[CmdletBinding()]
param(
    [Parameter(Mandatory)] [string] $TenantId,
    [Parameter(Mandatory)] [string] $ClientId,
    [Parameter(Mandatory)] [string] $CertificateThumbprint,
    [string] $OutputCsv = ".\out\$($MyInvocation.MyCommand.BaseName)-$(Get-Date -f yyyyMMdd).csv"
)

$ErrorActionPreference = 'Stop'
Import-Module Microsoft.Graph.Users, Microsoft.Graph.Identity.SignIns

try {
    Connect-MgGraph `
        -TenantId   $TenantId `
        -ClientId   $ClientId `
        -CertificateThumbprint $CertificateThumbprint `
        -NoWelcome | Out-Null

    # --- query goes here ---
    $rows = Get-MgUser -Filter "accountEnabled eq true" `
                       -ConsistencyLevel eventual `
                       -CountVariable c `
                       -Property Id,DisplayName,UserPrincipalName,SignInActivity `
                       -All

    $rows |
        Select DisplayName, UserPrincipalName,
               @{N='LastSignIn'; E={$_.SignInActivity.LastSignInDateTime}} |
        Export-Csv -Path $OutputCsv -NoTypeInformation

    Write-Host "Wrote $($rows.Count) rows to $OutputCsv"
}
catch {
    Write-Error "Graph query failed: $($_.Exception.Message)"
    throw
}
finally {
    Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
}
  1. Parameters first. Mandatory tenant, client and thumbprint โ€” never hard-coded. CI passes them as environment variables.
  2. Strict mode. $ErrorActionPreference = 'Stop' turns Graph's helpful warnings into actionable failures.
  3. Server-side filter. Always project columns and filter at the API; pulling everything and filtering in PowerShell is the #1 cause of throttling.
  4. Structured output. CSV for handoff to ops, JSON if downstream automation reads it.
  5. Disconnect in finally. Leaks of authenticated sessions are the second-most-common cause of "the script slows down over time".

Sustaining a Graph automation practice

The first Graph script in a tenant is exciting. The fortieth, written by the fifteenth different operator, is where the practice either matures or collapses into chaos. The teams that scale Graph automation share four operational habits worth copying.

The first habit is a shared module repository. Common patterns โ€” the connect wrapper, the throttling-aware retry, the standard CSV output formatter โ€” live in one signed module that every script imports. Updating the retry logic is a one-PR change instead of a search-and-replace across thirty repositories. Operators who write a fifth script start to feel the value; operators who write a fiftieth cannot imagine working without it.

The second is a permission catalogue. Every app registration in your tenant is documented in a single spreadsheet: app id, owner, business purpose, granted permissions, last reviewed date. The catalogue makes the quarterly access review tractable; without it, the review is "scroll through 200 apps in the portal and hope nothing slipped past". With it, sorting by last-reviewed-date and rejecting stale entries takes an hour.

The third is scheduled audit jobs. The audits described in the related cheat sheets โ€” MFA, license, conditional access, mailbox forwarding โ€” should each run on a schedule and email a one-page summary to the owner. Daily for security-sensitive audits, weekly for cost-focused ones. The pattern catches drift in days instead of months.

The fourth is throttling discipline. Graph throttles per-tenant on a sliding window. A single misbehaving script can starve every other automation in the tenant for an hour. Wrap every loop with backoff that honours the Retry-After header, log throttle events to a central place, and review them monthly. The first throttle event is informational; the tenth in a week is a script that needs refactoring.

Together these habits turn Graph automation from a series of one-off scripts into a sustainable platform. The investment is modest โ€” perhaps a week of an engineer's time spread over three months โ€” and the returns compound for years.

FAQ

Can I still use AzureAD?

It works through 2024 but is deprecated. Migrate now โ€” most cmdlets have a clear Graph SDK equivalent.

Does it work on PowerShell 5.1?

Yes, but PS7 is faster and is the recommended platform.

What about Exchange Online cmdlets?

The ExchangeOnlineManagement module is still required for the Exchange-specific cmdlets โ€” Graph has not yet covered every endpoint.

How do I switch tenants in the same session?

Disconnect-MgGraph then Connect-MgGraph -TenantId <new>. There is no multi-tenant context.

Microsoft Graph PowerShell SDK or AzureAD module?

Graph SDK. The AzureAD and MSOnline modules are deprecated and stop receiving updates in 2025. New scripts should be Graph-native; existing scripts should be migrated on a planned schedule.

Can I use the Graph SDK from PowerShell 5.1?

It installs but several cmdlets misbehave around async paging and error handling. Use PowerShell 7 in production. PowerShell 5.1 is fine for one-off interactive work.

How do I see what HTTP request a cmdlet actually issues?

Set-MgRequestContext -DebugLogging $true and re-run. The full request URL, headers and body land in the verbose stream โ€” invaluable for debugging filter syntax.

What is the Beta endpoint and when do I use it?

Beta exposes preview functionality (e.g. some Conditional Access fields). Switch with Select-MgProfile -Name 'beta'. Acceptable for read-only reporting; risky for write operations because the schema can change.

Related Dargslan resources

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.