PowerShell Microsoft Graph API: Get M365 Data

PowerShell Microsoft Graph API: Get M365 Data

PowerShell Tips Editor 4 min read
PowerShell Microsoft Graph API: Get M365 Data

One API to access users, emails, Teams, SharePoint, calendar, and every other Microsoft 365 service — that’s the promise of the PowerShell Microsoft Graph API integration. Graph replaces the fragmented per-service PowerShell modules with a unified REST surface that is actively developed and will remain the strategic path forward. Whether you authenticate with a token and call the REST endpoints directly, or use the Microsoft.Graph PowerShell SDK, this post gives you everything you need to start pulling real M365 data.

Register an Azure AD App

Before calling Graph, you need an Azure AD app registration that grants API permissions. Do this once in the Azure Portal under Azure Active Directory > App Registrations > New Registration. Key steps:

  • Create a new app registration (any name, single-tenant is fine)
  • Under API Permissions, add Microsoft Graph > Application permissions (for unattended scripts) or Delegated permissions (for user context)
  • Common permissions: User.Read.All, Group.Read.All, Mail.Read
  • Grant admin consent for your tenant
  • Create a client secret or upload a certificate under Certificates & Secrets

Get an Access Token

Acquire an OAuth 2.0 access token using the client credentials flow. This is the app-only (non-user) authentication pattern for automation scripts.

$tenantId     = "your-tenant-id"
$clientId     = "your-app-client-id"
$clientSecret = "your-client-secret"

$body = @{
    grant_type    = "client_credentials"
    client_id     = $clientId
    client_secret = $clientSecret
    scope         = "https://graph.microsoft.com/.default"
}

$tokenResponse = Invoke-RestMethod `
    -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
    -Method POST `
    -Body $body

$accessToken = $tokenResponse.access_token
Write-Output "Token acquired. Expires in $($tokenResponse.expires_in) seconds."

Call Graph API with Invoke-RestMethod

With the token in hand, call any Graph endpoint using Invoke-RestMethod. The token goes in the Authorization header as a Bearer token.

$headers = @{
    Authorization = "Bearer $accessToken"
    "Content-Type" = "application/json"
}

# Get current tenant organization info
$org = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/organization" -Headers $headers
Write-Output "Tenant: $($org.value[0].displayName)"

# Get a list of users
$users = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" -Headers $headers
$users.value | Select-Object displayName, userPrincipalName, id

Use Microsoft.Graph PowerShell SDK

The Microsoft.Graph module wraps the raw REST calls in strongly-typed cmdlets. It’s the recommended approach for interactive scripts because it handles authentication, token refresh, and pagination automatically.

# Install the module (large — install specific sub-modules as needed)
Install-Module -Name Microsoft.Graph.Users -Scope CurrentUser
Install-Module -Name Microsoft.Graph.Groups -Scope CurrentUser

# Connect interactively (MFA-compatible)
Connect-MgGraph -TenantId "your-tenant-id" -Scopes "User.Read.All", "Group.Read.All"

# Or connect with app credentials
$credential = New-Object System.Management.Automation.PSCredential($clientId,
    (ConvertTo-SecureString $clientSecret -AsPlainText -Force))
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential

Query Users and Groups

With the SDK, use the Graph cmdlets directly — they follow the same verb-noun convention as other PowerShell cmdlets.

# Get all users
Get-MgUser -All | Select-Object DisplayName, UserPrincipalName, AccountEnabled

# Filter users — syntax uses OData filter strings
Get-MgUser -Filter "department eq 'Engineering'" -All |
    Select-Object DisplayName, UserPrincipalName

# Get a specific user's group memberships
$userId = (Get-MgUser -UserId "[email protected]").Id
Get-MgUserMemberOf -UserId $userId | Select-Object -ExpandProperty AdditionalProperties |
    Where-Object { $_["@odata.type"] -eq "#microsoft.graph.group" } |
    ForEach-Object { $_["displayName"] }

Handle Pagination with @odata.nextLink

Graph API returns a maximum of 100 results per page by default. When more results exist, the response includes an @odata.nextLink property with the URL for the next page. Loop until nextLink is absent.

$headers  = @{ Authorization = "Bearer $accessToken" }
$url      = "https://graph.microsoft.com/v1.0/users?`$top=100"
$allUsers = @()

do {
    $response  = Invoke-RestMethod -Uri $url -Headers $headers
    $allUsers += $response.value
    $url       = $response.'@odata.nextLink'
} while ($url)

Write-Output "Total users retrieved: $($allUsers.Count)"

The Microsoft.Graph SDK cmdlets handle pagination automatically when you use the -All switch.

Common Errors and Fixes

  • Insufficient permissions: A “403 Forbidden” or “Insufficient privileges” response means your app registration lacks the required API permission for the endpoint you’re calling. Check the app’s API permissions in Azure AD, ensure you added application permissions (not just delegated), and verify admin consent was granted. Use the Graph Explorer at developer.microsoft.com/graph/graph-explorer with your credentials to test permission requirements interactively.
  • Token expiry: Access tokens expire after one hour (3600 seconds). Requests made with an expired token receive a 401 error. For long-running scripts, re-authenticate periodically or use the SDK which handles refresh tokens automatically. In the raw token pattern, record $tokenResponse.expires_in and re-request a token before expiry: check ((Get-Date) - $tokenAcquiredTime).TotalSeconds -gt 3500.

Related Cmdlets / See Also

Wrapping Up

The Microsoft Graph API unified all M365 services under a single authentication model and REST surface — mastering it unlocks automation across the entire Microsoft 365 platform. As a next step, implement the pagination loop above in a reusable helper function and use it to build a complete user export that includes manager, department, license, and last sign-in data in a single API call chain.

Send-Item -To