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_inand 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.


