Skip to content
6 min read·Lesson 6 of 8

Microsoft Graph and Microsoft 365

The Microsoft Graph PowerShell SDK — the unified API for Entra ID, Exchange, Teams, SharePoint, Intune, and the rest of M365.

Microsoft Graph is the unified REST API for the Microsoft 365 / Entra / Intune universe. It replaced a dozen service-specific APIs. The Microsoft Graph PowerShell SDK wraps it in cmdlets — and supersedes a long list of older PowerShell modules:

Legacy moduleReplaced by
MSOnline (Connect-MsolService)Microsoft.Graph.*
AzureAD / AzureADPreviewMicrosoft.Graph.*
Some Exchange Online cmdletsMicrosoft.Graph.*
Intune PowerShell SDK (older)Microsoft.Graph.DeviceManagement

Migrate from MSOnline / AzureAD — Microsoft has been deprecating them since 2024.

Installation

# Install just what you need — the full meta-module is huge.
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.DirectoryObjects -Scope CurrentUser

# Or the lot (large download):
Install-Module Microsoft.Graph -Scope CurrentUser

Connecting

Interactive (with scopes)

Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All", "Directory.Read.All"

Get-MgContext
Disconnect-MgGraph

Scopes are Microsoft Graph permission strings. Browse them in the Graph Explorer — also the best place to test API calls before scripting them.

App-only with certificate (production)

# Register an app in Entra ID, grant it application permissions (not delegated), upload a certificate.

Connect-MgGraph -ClientId $clientId -TenantId $tenantId -CertificateThumbprint $thumbprint

App-only auth uses application permissions — broader scope, no signed-in user. Use a Key Vault-stored certificate, not a client secret.

Managed Identity

Connect-MgGraph -Identity

The MI must have Graph application permissions granted. Best choice from Azure Functions, Azure Automation, VMs.

Working with Users

# List
Get-MgUser -All | Select-Object DisplayName, UserPrincipalName, Department

# Filter (Graph $filter syntax — different from Where-Object)
Get-MgUser -Filter "department eq 'Engineering'"
Get-MgUser -Filter "startsWith(displayName,'Alice')"

# Get one
$user = Get-MgUser -UserId alice@acme.io
$user | Get-Member

# Update
Update-MgUser -UserId alice@acme.io -Department "Platform"

# Create
$password = @{ ForceChangePasswordNextSignIn = $true; Password = "TempPa55w0rd!" }
New-MgUser -DisplayName "Bob Smith" -UserPrincipalName bob@acme.io `
    -AccountEnabled -MailNickname bob -PasswordProfile $password

# Disable
Update-MgUser -UserId bob@acme.io -AccountEnabled:$false

Groups and Membership

# Create a security group
$group = New-MgGroup -DisplayName "Engineering" -MailNickname engineering `
    -SecurityEnabled -MailEnabled:$false

# Add members
$user = Get-MgUser -UserId alice@acme.io
New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $user.Id

# List members
Get-MgGroupMember -GroupId $group.Id -All | ForEach-Object {
    Get-MgUser -UserId $_.Id
}

# Bulk import from CSV
Import-Csv ./new-hires.csv | ForEach-Object {
    $u = Get-MgUser -UserId $_.email
    New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $u.Id
}

Conditional Access Policies

Install-Module Microsoft.Graph.Identity.SignIns -Scope CurrentUser

Get-MgIdentityConditionalAccessPolicy | Where-Object State -eq enabled |
    Select-Object DisplayName, State, CreatedDateTime |
    Sort-Object DisplayName

Conditional Access policies are full-blown objects with nested conditions and grants — easy to audit, deploy, or version-control as JSON.

Devices and Intune

Install-Module Microsoft.Graph.DeviceManagement -Scope CurrentUser

# Inventory
Get-MgDeviceManagementManagedDevice -All |
    Select-Object DeviceName, OperatingSystem, ComplianceState, UserPrincipalName |
    Where-Object ComplianceState -ne "compliant" |
    Export-Csv ./non-compliant-devices.csv -NoTypeInformation

# Trigger sync
$device = Get-MgDeviceManagementManagedDevice -Filter "deviceName eq 'JANE-LAPTOP'"
Sync-MgDeviceManagementManagedDevice -ManagedDeviceId $device.Id

The Generic Invoke-MgGraphRequest

When no cmdlet exists, call the API directly:

$res = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/me/messages?$top=5"
$res.value | Select-Object subject, receivedDateTime

# POST with body
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/groups" -Body @{
    displayName = "DevOps"
    mailNickname = "devops"
    securityEnabled = $true
    mailEnabled = $false
} -ContentType "application/json"

Useful for beta endpoints (v1.0beta in URL) and for endpoints not yet covered by a cmdlet.

Exchange Online and SharePoint

The Exchange Online and SharePoint estates have their own remote PowerShell modules with Graph-style features:

  • ExchangeOnlineManagement — mailbox management, message trace, transport rules. Connect-ExchangeOnline.
  • PnP.PowerShell — SharePoint, Teams, lists. Connect-PnPOnline. Far richer than Graph for SharePoint operations.
  • MicrosoftTeams — Teams management.

Long-term direction: more of this moves to Graph each year. For now, use the dedicated modules where they have better coverage.

Practical Patterns

Audit report — inactive users

Connect-MgGraph -Scopes "AuditLog.Read.All", "User.Read.All"

$thresholdDays = 90
$inactive = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, SignInActivity |
    Where-Object {
        $signin = $_.SignInActivity.LastSignInDateTime
        -not $signin -or ((New-TimeSpan -Start $signin -End (Get-Date)).Days -gt $thresholdDays)
    } |
    Select-Object DisplayName, UserPrincipalName, @{
        Name="LastSignIn"
        Expression={ if ($_.SignInActivity.LastSignInDateTime) { $_.SignInActivity.LastSignInDateTime } else { "never" } }
    }

$inactive | Export-Csv ./inactive-users.csv -NoTypeInformation

Bulk MFA enforcement

$policy = Get-MgPolicyAuthenticationMethodPolicy
# (most MFA enforcement is policy-driven via Conditional Access now)

What to Watch Out For

  • Throttling. Graph throttles aggressively at ~1000 req/min per app. Handle 429s with Retry-After.
  • $filter vs Where-Object. Filter at the API (-Filter) — far faster than fetching all and filtering locally.
  • Permissions sprawl. Grant the minimum scopes; app permissions especially are global.
  • Module size. The meta-module loads slowly. Use sub-modules.
  • beta vs v1.0. Cmdlets use v1.0 by default. Use Select-MgProfile beta (older API) or -Uri with /beta/ for preview features.

Learning Resources

Az + Graph PowerShell together cover essentially every M365 / Azure automation need. The last two lessons cover how to actually run these scripts — Automation Accounts, Functions, CI — and how to write them cross-platform.

Key Takeaways

  • Microsoft Graph PowerShell replaces MSOnline, AzureAD, and many older M365 modules.
  • Connect with delegated scopes for interactive use, app-only with certificate or federated credential for automation.
  • Cmdlets are auto-generated from the Graph API — Get-MgUser, New-MgGroup, Update-MgDeviceManagementDevice.
  • Use specific sub-modules to keep startup fast — installing the full Microsoft.Graph metamodule is rarely needed.
  • Graph Explorer is the best way to discover endpoints and test calls before writing code.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →