Practical engineering guidance
Microsoft Graph PowerShell: authentication, scopes and least privilege
The SDK asks for the permissions you name, and most people name too many. Two discovery cmdlets tell you exactly what a command needs, which makes least privilege straightforward.
Series: PowerShell toolkit
The Microsoft Graph PowerShell SDK is how most administrative automation against Microsoft 365 gets written now. The common mistake is in the first line: connecting with a broad set of scopes because it is easier than finding out which narrow ones are needed.
The SDK contains the tools to avoid that, and they take about a minute to use.
The problem: consented scope is durable
When you run Connect-MgGraph -Scopes interactively and consent, the permission is granted to
the Microsoft Graph PowerShell application in your tenant. It persists. Consenting to
Directory.ReadWrite.All once, for a script that only needed to read users, leaves that
permission available to anyone using the SDK under that consent thereafter.
That is why scope selection is a security decision and not a convenience one. It is the same issue described in application consent in Microsoft Entra ID, arriving through a command line rather than a consent prompt.
The data model: three ways to authenticate
Interactive, delegated. You sign in, and the script acts as you, bounded by both the scope and your own permissions. Right for ad-hoc administration and investigation.
Connect-MgGraph -Scopes 'User.Read.All', 'AuditLog.Read.All' -NoWelcome
App-only with a certificate. The script acts as an application, with application permissions, bounded by nothing but those permissions. Right for unattended automation.
Connect-MgGraph -ClientId $appId -TenantId $tenantId `
-CertificateThumbprint $thumbprint -NoWelcome
Use a certificate rather than a client secret. A secret is a password that ends up in a script, a pipeline variable or a configuration file, and it will outlive the person who created it.
Managed identity. Where the script runs in Azure — an automation account, a function, a virtual machine — a managed identity removes the credential entirely.
Connect-MgGraph -Identity -NoWelcome
This is the best option wherever it is available, because there is no credential to store, rotate or leak.
Finding the right scope, which is the point
Two cmdlets answer the question properly, and between them they remove any excuse for over-requesting.
Find-MgGraphCommand tells you what a command calls and what permissions it needs:
Find-MgGraphCommand -Command 'Get-MgUser' |
Select-Object -ExpandProperty Permissions |
Select-Object Name, IsAdmin, Description -Unique |
Sort-Object Name
It also works in the other direction — give it a Graph URI and it names the cmdlet, which is how you translate documentation written against the REST API into SDK commands:
Find-MgGraphCommand -Uri '/users/{id}/authentication/methods' |
Select-Object Command, Method, APIVersion
Find-MgGraphPermission searches the permission catalogue by keyword, which is how you
find the narrower alternative to the one you first thought of:
Find-MgGraphPermission -SearchString 'user' -PermissionType Delegated |
Select-Object Name, Description |
Sort-Object Name
The pattern that follows from these: write the command first, ask what it needs, then connect with exactly that. Not the other way round.
Get-MgContext tells you what you are currently connected with, which is worth checking
before running anything consequential:
Get-MgContext | Select-Object Account, TenantId, AppName, AuthType, Scopes
If the scopes list is longer than the task requires, disconnect and reconnect with less.
The scopes worth knowing
A few principles rather than a catalogue, because the catalogue is large and changes.
.Read.All before .ReadWrite.All. Most scripts read. A script that reads should never be
connected with write permission, because a mistake in a read-only session cannot damage
anything.
Prefer the specific resource scope. User.Read.All rather than Directory.Read.All where
you only need users. Directory.Read.All is broad and includes a great deal you did not ask
for.
Know which scopes are effectively privileged. Directory.ReadWrite.All,
RoleManagement.ReadWrite.Directory and AppRoleAssignment.ReadWrite.All are
privilege-escalation capable. Treat consent to any of them as a Tier 0 change.
Delegated does not mean safe. A delegated scope is bounded by your own permissions, which for a Global Administrator is not much of a bound. The scope is doing the limiting, so choose it carefully.
Some specific pairings that come up constantly, all confirmed against current documentation:
| Task | Delegated scope |
|---|---|
| Read users | User.Read.All |
| Read sign-in logs | AuditLog.Read.All |
| Read directory audit logs | AuditLog.Read.All |
| Read risky users | IdentityRiskyUser.Read.All |
| Read Conditional Access policies | Policy.Read.All |
| Read applications and service principals | Application.Read.All |
Note that reading sign-in and audit logs also requires an appropriate directory role — Security Reader or Reports Reader at minimum. The scope grants the application permission; the role grants you the right. Both are needed, and a missing role produces an error that reads like a permission problem and is not.
Practical structure for a script
#requires -Modules Microsoft.Graph.Authentication
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$OutputPath
)
$scopes = @('User.Read.All')
try {
Connect-MgGraph -Scopes $scopes -NoWelcome -ErrorAction Stop
$context = Get-MgContext
Write-Verbose "Connected as $($context.Account) with: $($context.Scopes -join ', ')"
Get-MgUser -All -Property 'id', 'displayName', 'userPrincipalName', 'accountEnabled' |
Select-Object Id, DisplayName, UserPrincipalName, AccountEnabled |
Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
}
finally {
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
}
Three things worth copying from that shape:
-Propertyon the retrieval. Graph returns a default property set, and a property you did not request comes back empty rather than erroring — which produces a report full of blank columns and a confusing half-hour. Request what you need explicitly.-Allfor paging. Graph pages results. Without-Allyou get the first page and no indication that there is more, which silently under-reports.Disconnect-MgGraphin afinallyblock. Leaving a session connected on a shared machine leaves a token behind.
Interpretation and extension
Two behaviours cause most of the confusion when a script does not return what you expect.
Empty properties. Almost always the missing -Property above, not a permissions problem.
Missing results. Almost always paging. Check for -All before investigating anything else.
To extend beyond what the v1.0 cmdlets cover, the beta modules expose preview APIs with
Get-MgBeta* cmdlets. They are useful and they are preview — behaviour can change, so do not
build an unattended process on them without accepting that.
Operational cautions
Do not consent on behalf of the organisation casually. The consent prompt offers it, and accepting grants the permission tenant-wide for the SDK. Consent for yourself unless you deliberately intend the broader grant.
Review what the SDK application has been consented to, periodically. It accumulates, and the accumulated set is what any user of the SDK can then exercise.
Use a dedicated application registration for unattended automation, with only the application permissions that automation needs, rather than the shared SDK application.
Verification and limits
Connect-MgGraph authentication options including certificate and managed identity,
Find-MgGraphCommand, Find-MgGraphPermission, Get-MgContext, and the delegated permissions
for Get-MgUser, Get-MgAuditLogSignIn and Get-MgRiskyUser — including the directory role
requirement for activity logs — were checked against current Microsoft documentation on
20 September 2026.
The scripts were not executed against a tenant for this article. Everything shown is read-only,
but consenting to a scope grants a durable permission in your tenant: connect with the
narrowest scope that works, confirm with Find-MgGraphCommand rather than guessing, and avoid
consenting on behalf of the organisation unless you intend to. Module coverage and required
permissions change between SDK versions.
References
Reader feedback
Was this article useful?
No ratings yet. Be the first to rate this article.
