An Introduction to Microsoft Graph PowerShell — Running Microsoft 365 After the Retirement of AzureAD and MSOnline
· Go Komura · PowerShell, Microsoft 365, Microsoft Entra ID, Microsoft Graph, Information Systems, Automation, Operational Improvement, Security
For anyone automating Microsoft 365 operations, the biggest change across 2024 and 2025 was the retirement of the AzureAD and MSOnline modules. Plenty of IT departments wrote their routine work — leaver processing, license inventories, creating accounts for new joiners — with Get-MsolUser or Get-AzureADUser, and those scripts are progressively stopping working.
The migration target is the Microsoft Graph PowerShell SDK. But this isn’t simply a matter of swapping command names. The premises of the design change: how authentication works (scopes and consent), how you retrieve data (OData filters and paging), and how you build unattended execution (app registration and certificates). Replace things mechanically without understanding that, and you take on a different set of problems — “it works, but it’s running with excessive permissions,” or “on a large tenant we only get part of the data.”
This article is aimed at IT staff automating in-house Microsoft 365 operations with PowerShell. It covers the background to the retirements, connecting and designing scopes, configuring unattended execution, and three standard recipes: inventory, license summary, and leaver processing — all in a form you can use in practice.
1. The Bottom Line First
- MSOnline and AzureAD were deprecated on 30 March 2024; MSOnline reached end of availability on 30 May 2025, and AzureAD was retired after going out of support on 30 March 2025.1
- The migration target is the Microsoft Graph PowerShell SDK, or Microsoft Entra PowerShell built on top of it (GA in March 2025). The latter is scenario-oriented and also offers compatibility options to ease migration from AzureAD.2
Microsoft.Graphis a meta-module. Installing all of it is heavy, so in practice you installMicrosoft.Graph.Authenticationplus only the submodules for the workloads you use.3- Connections start with
Connect-MgGraph -Scopes. Keep scopes to least privilege. You can look up the permissions you need withFind-MgGraphPermission, and which module a command belongs to withFind-MgGraphCommand.45 - Unattended execution means app-only authentication with an app registration plus a certificate. Connect without interaction using
-ClientId,-TenantIdand-CertificateThumbprint. A certificate is recommended over a client secret.46 - Delegated and application (app-only) permissions are different things. The scope required for the same operation changes, so when you switch to unattended execution you have to re-grant the permissions.6
- Use
-Allfor listing,-Filterfor narrowing, and-Propertyfor fields. Narrowing client-side withWhere-Objectcauses wasted retrieval and throttling.7 - Heavy access gets throttled. For a 429 response, the official guidance is to wait according to
Retry-After.8 - Separate app registrations by purpose. A single “does everything” app accumulates permissions and widens the blast radius when something goes wrong.
2. The Background to the Retirements, and What to Choose Now
First, the facts.1
| Module | Status |
|---|---|
MSOnline (Get-MsolUser and so on) |
Deprecated 30 March 2024. Retired 30 May 2025 |
AzureAD (Get-AzureADUser and so on) |
Deprecated 30 March 2024. Out of support 30 March 2025, retired afterwards |
| Microsoft Graph PowerShell SDK | Current. The Graph API turned directly into cmdlets |
| Microsoft Entra PowerShell | GA in March 2025. A scenario-oriented module built on the Graph SDK2 |
Here’s the guideline for which to use. If you want to work with the structure of the Graph API directly, or to touch a broad range of workloads (Exchange, Teams, Intune and so on), use the Graph PowerShell SDK. If your focus is identity management in Entra ID (formerly Azure AD) and you want to make migration from the AzureAD module as painless as possible, use Microsoft Entra PowerShell. The latter interoperates with the Graph PowerShell SDK and also provides backwards-compatibility options to assist migration from the AzureAD module.2
This article is built around the Graph PowerShell SDK, which is more general-purpose and better documented.
3. Installation — Don’t Install the Whole Meta-Module
Microsoft.Graph is a meta-module bundling a large number of submodules. Installing the whole thing is heavy both to install and to load, and depending on the execution environment, loading alone can take tens of seconds.3
# [Heavy] Install every workload
Install-Module Microsoft.Graph -Scope CurrentUser
# [Practical] Install authentication plus only the workloads you use
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser # Mandatory
Install-Module Microsoft.Graph.Users -Scope CurrentUser # Users
Install-Module Microsoft.Graph.Groups -Scope CurrentUser # Groups
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser # Licenses etc.
Install-Module Microsoft.Graph.Users.Actions -Scope CurrentUser # Actions on users
# (Revoke-MgUserSignInSession etc.)
# Find out which module a command lives in, and what permissions it needs
Find-MgGraphCommand -Command Get-MgUser | Select-Object Module, Permissions -First 1
Find-MgGraphPermission user.read -PermissionType Delegated
Use on PowerShell 7 is recommended. It works on Windows PowerShell 5.1 too, but this is an area where there are strong reasons to choose 7 both for performance and for future-proofing (see “The Differences Between Windows PowerShell 5.1 and PowerShell 7”).3
4. Connecting and Scopes — Stop Reaching for ReadWrite.All
Interactive connection is Connect-MgGraph -Scopes. A consent screen appears for the scopes you specify, and the result of that consent is recorded in the tenant.4
# Read-only inventory use. Don't request write permissions
Connect-MgGraph -Scopes 'User.Read.All', 'Organization.Read.All' -NoWelcome
Get-MgContext | Format-List Account, TenantId, Scopes, AuthType # Check the current connection
Disconnect-MgGraph
This is where migration makes the biggest difference. In the Get-MsolUser era it was “sign in with an admin account and you can do everything,” but in Graph the required scope is defined per operation, and things only work within the range you’ve consented to. That’s not a restriction, it’s a safety device. An inventory script can’t accidentally perform a write if you’ve only consented to .Read.All.
There are three principles.
- Don’t request write scopes for read-only work (if
User.Read.Allis enough, don’t ask forUser.ReadWrite.All) - Separate app registrations by purpose (one for inventories, one for account creation, one for license management)
- Have an administrator grant consent deliberately (consent, once granted, stays in the tenant)
When you don’t know which scope you need, look for candidates with Find-MgGraphPermission and check the permissions a command requires with Find-MgGraphCommand.5
5. Unattended Execution — App Registration Plus a Certificate
If it runs from Task Scheduler every night, interactive sign-in is out. Switch to app-only authentication with an app registration (service principal) and a certificate.6
The skeleton of the procedure is as follows.
- Register an app in the Microsoft Entra admin center
- Add only the minimum necessary application permissions, and grant admin consent
- Create a certificate, upload the public key to the app registration, and place the private key in the executing account’s certificate store
- Connect from the script with
-CertificateThumbprint
# Connection for unattended execution (no interaction)
$connect = @{
ClientId = '11111111-2222-3333-4444-555555555555'
TenantId = '66666666-7777-8888-9999-000000000000'
CertificateThumbprint = 'A1B2C3D4E5F6...' # A certificate in the executing account's store
NoWelcome = $true
}
Connect-MgGraph @connect
try {
# Business logic
}
finally {
Disconnect-MgGraph
}
There are two things to watch out for.
(1) Delegated and app-only need different permissions. When you make a script that worked interactively with -Scopes 'User.Read.All' unattended, you have to re-grant the same kind of permission on the app registration side as an application permission, with admin consent.6
(2) Certificates expire. The nightly batch dying wholesale on the day a certificate expires is a genuinely common accident. Put the expiry date in a calendar, and document the renewal procedure. For storing credentials, see also “Handling Credentials Safely in PowerShell.” You can also connect with a client secret, but given the risk of passing it around in plaintext and the awkwardness of expiry management, a certificate is recommended.
6. Retrieving Data — -All / -Filter / -Property
Graph is an API built around paging. By default only one page comes back, so if you need everything, add -All.7
# [Bad] Ignore paging and filter client-side (slow, retrieves too much, causes throttling)
Get-MgUser | Where-Object { $_.Department -eq 'Sales' }
# [Good] Filter server-side, take only the fields you need, and retrieve every page
Get-MgUser -All -Filter "department eq 'Sales'" `
-Property Id, DisplayName, UserPrincipalName, AccountEnabled, Department |
Select-Object DisplayName, UserPrincipalName, AccountEnabled
Three points matter here.
-Filteris an OData expression and narrows server-side.Where-Objectnarrows locally, which means retrieving everything and then throwing most of it away- Narrowing columns with
-Propertymakes the response lighter. Some properties that aren’t returned by default are returned if you name them explicitly - Fields retrieved with
-Propertyalso have to appear inSelect-Object. Retrieval and display are separate, so specifying only one of them leaves blanks
For advanced queries such as startsWith or endsWith, or when you only want a count, combine -ConsistencyLevel eventual with -CountVariable.7
# Count only the enabled users (get the count without retrieving everything)
Get-MgUser -Filter 'accountEnabled eq true' -ConsistencyLevel eventual -CountVariable total -Top 1 | Out-Null
"Enabled users: $total"
Heavy access gets throttled, returning HTTP 429. The official guidance is to “wait for the number of seconds in the Retry-After header before retrying.”8 The SDK’s cmdlets do some retrying internally, but when you’re running loops over thousands of items, reducing the number of retrievals in the first place (narrowing with -Filter and -Property, getting the information you need in a single call) is the reliable approach. For retry design itself, see “PowerShell Error Handling and Retry Design.”
7. Three Standard Recipes
(1) Export a User Inventory to CSV
# SignInActivity (last sign-in timestamp) can't be retrieved with User.Read.All alone;
# AuditLog.Read.All is required as well. If you're not collecting it, drop it from -Property
Connect-MgGraph -Scopes 'User.Read.All', 'AuditLog.Read.All' -NoWelcome
$users = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, AccountEnabled,
Department, JobTitle, CreatedDateTime, SignInActivity |
Select-Object DisplayName, UserPrincipalName, Department, JobTitle, AccountEnabled,
@{ n = 'Created'; e = { $_.CreatedDateTime } },
# Use "successful sign-ins" to judge dormancy. LastSignInDateTime is the
# "attempt" at an interactive sign-in, includes failures, and excludes
# non-interactive sign-ins
@{ n = 'LastSuccessfulSignIn'; e = { $_.SignInActivity.LastSuccessfulSignInDateTime } },
@{ n = 'LastInteractiveSignInAttempt'; e = { $_.SignInActivity.LastSignInDateTime } },
@{ n = 'LastNonInteractiveSignIn'; e = { $_.SignInActivity.LastNonInteractiveSignInDateTime } }
# A CSV containing non-ASCII text won't be garbled in Excel if written as UTF-8 with BOM.
# The encoding name differs by version: utf8BOM on 7+, UTF8 (with BOM) on 5.1
$enc = if ($PSVersionTable.PSVersion.Major -ge 6) { 'utf8BOM' } else { 'UTF8' }
$users | Export-Csv -Path "D:\Inventory\users_$(Get-Date -f yyyyMMdd).csv" -Encoding $enc -NoTypeInformation
SignInActivity is effective for flushing out dormant accounts. But which field you look at matters. LastSignInDateTime records attempts at interactive sign-in (including failures) and doesn’t include non-interactive sign-ins by apps or services. Judging on that alone can make an account look “in use” because an attacker failed to log in, or make an actively running service account look “dormant.” For dormancy, use LastSuccessfulSignInDateTime, which reflects successful interactive and non-interactive sign-ins.9 Note, however, that this property alone cannot be retrieved with User.Read.All — AuditLog.Read.All is additionally required (and there are licensing requirements on the tenant side as well). If the permissions are insufficient you’ll either get an error or an empty value, so if you’re not using it, drop it from -Property. You can check the required permissions with Find-MgGraphPermission.9 Handling CSV character encoding is covered in “Automating Excel and CSV Business Tasks with PowerShell.”
(2) Summarize License Consumption
Connect-MgGraph -Scopes 'Organization.Read.All' -NoWelcome
Get-MgSubscribedSku | Select-Object `
SkuPartNumber,
@{ n = 'Purchased'; e = { $_.PrepaidUnits.Enabled } },
@{ n = 'Assigned'; e = { $_.ConsumedUnits } },
@{ n = 'Available'; e = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } |
Sort-Object Available
“We bought more licenses even though we had spares” and “leavers’ licenses were never released” are both preventable just by running this monthly.
(3) Leaver Processing (Blocking Sign-In and Revoking Sessions)
# This involves writes, so run it under a dedicated app registration with dedicated scopes.
# Omitting -ClientId connects via the SDK's default shared app, and the permissions you
# consent to pile up on that app (which is shared across the organization). Destructive
# operations like leaver processing are exactly where permissions should be isolated
# in a dedicated app registration
#
# The required permission differs per operation, so request both operations' permissions together
# Changing accountEnabled -> User.EnableDisableAccount.All (least privilege)
# Revoking sign-in sessions -> User.RevokeSessions.All (least privilege)
# Both can also be done with User.ReadWrite.All, but that grants broader permissions
$connect = @{
ClientId = '99999999-aaaa-bbbb-cccc-dddddddddddd' # App registration dedicated to leaver processing
TenantId = '66666666-7777-8888-9999-000000000000'
Scopes = 'User.Read.All', 'User.EnableDisableAccount.All', 'User.RevokeSessions.All'
NoWelcome = $true
}
Connect-MgGraph @connect
# Revoke-MgUserSignInSession requires Microsoft.Graph.Users.Actions
Import-Module Microsoft.Graph.Users.Actions
$upn = 'taro.yamada@example.co.jp'
$user = Get-MgUser -UserId $upn -Property Id, DisplayName, AccountEnabled
# 1. Block sign-in (deletion comes later, after a grace period)
Update-MgUser -UserId $user.Id -AccountEnabled:$false
# 2. Revoke refresh tokens and browser session cookies
# Note: already-issued access tokens may remain usable until they expire (see below)
Revoke-MgUserSignInSession -UserId $user.Id
Write-Host "Blocked sign-in for $($user.DisplayName)"
There are three things to internalize here. The first is which app registration you connect to. Omitting -ClientId connects via the Microsoft Graph PowerShell SDK’s default app, and the permissions you consent to are recorded against that app, which is shared across the organization. To put the “separate app registrations by purpose” principle from Section 4 into practice, you need to name your own app registration with -ClientId.4
The second is scopes. In Graph the required permission is defined per operation — “I can write users, so I can do anything” is not how it works. Changing accountEnabled and revoking sign-in sessions each have their own least-privilege permission, so if you consent to only one and run the script, the connection succeeds but a command partway through fails with an insufficient-permissions error. Check the permissions required for each operation with Find-MgGraphPermission and in the permissions table of the corresponding Graph API reference.1011
And third, in delegated execution, scopes alone aren’t enough. When you run this signed in as yourself, as above, changing accountEnabled also requires a Microsoft Entra administrator role. That’s because a delegated permission only determines “what the app can do on that user’s behalf” — it does not elevate the signed-in user’s own privileges. If you’ve consented to the permission but lack the role, execution fails with a 403. The documentation gives Privileged Authentication Administrator as the least-privileged role that can update this property for all administrators in the tenant, and states more generally that an administrator role higher than the target’s is required.10 Because the role you need changes depending on whether the leaver is an ordinary user or an administrator, decide in advance what to assign to the operations team’s accounts. Even with certificate-based app-only execution, if the target is an administrator, the app itself needs a higher administrator role assigned to it.10
You also need to understand precisely what Revoke-MgUserSignInSession does. What this command invalidates is refresh tokens and browser session cookies; access tokens already issued may remain usable until they expire.11 If you need immediate cut-off, the premise is that the applications and resources involved support continuous access evaluation (CAE). Don’t assume “I revoked it, so all access stops instantly” — in important cases, combine it with disabling the account, and factor in the delay before it takes effect.
Avoid deleting the account immediately; block sign-in first — that’s the practical rule. Deleting before mailbox and OneDrive handover has finished makes recovery a chore. For this kind of irreversible operation, it’s safer to wrap it in your own function that supports -WhatIf, and to output the list of targets for review before executing (see “PowerShell Parameter Design and Modularization”).
8. Practical Rules of Thumb (Decision Table)
| Question | Options | Guideline |
|---|---|---|
| Module | Graph SDK / Entra PowerShell | Entra if you’re focused on identity management and migrating from AzureAD; the Graph SDK for a broad range of workloads2 |
| Installation | Everything / Submodules only | Per submodule, to keep startup time and dependencies down3 |
| Authentication (interactive) | Leave it to the admin account / Minimum scopes | Inventories need only .Read. permissions. Consent stays in the tenant4 |
| Authentication (unattended) | Client secret / Certificate | Certificate recommended. Decide the expiry management and renewal procedure up front6 |
| Permission granularity | One app with everything / An app registration per purpose | Limits the blast radius when something goes wrong |
| Listing | Where-Object / -Filter + -All + -Property |
Narrow server-side. The volume retrieved directly determines speed and stability7 |
| Handling 429 | Retry immediately / Follow Retry-After |
Official guidance. Reducing the number of requests comes first8 |
| Dangerous operations | Run directly / -WhatIf + review the target list first |
Leaver processing and bulk deletion must always let you eyeball the targets |
9. Summary
- MSOnline and AzureAD have been retired. The migration targets are the Microsoft Graph PowerShell SDK, or Microsoft Entra PowerShell on top of it.
Microsoft.Graphis a meta-module, so in practice install authentication plus only the submodules for the workloads you use.- Minimising scopes is the principle for connecting. Don’t request write permissions for inventory work, and separate app registrations by purpose.
- Unattended execution means an app registration plus a certificate. The fact that delegated and app-only need different permissions, and that certificates expire, are both sources of accidents.
- Data retrieval is a set of three:
-All(paging),-Filter(server-side narrowing) and-Property(limiting fields). For 429, followRetry-After. - Simply running the three recipes — inventory, license summary and leaver processing — on a monthly cycle substantially reduces the two most common risks: wasted licenses and abandoned accounts.
Sample Code Download
The code covered in this article is packaged in a ready-to-run form. It includes connecting, extracting dormant accounts, summarising licenses, and leaver processing.
Download the sample code (zip)
Because these samples depend on Windows and on a tenant, they have not been verified by actually running them. Syntax parsing and static analysis with PSScriptAnalyzer have been carried out against every file, but please confirm the behaviour on your own test machine.
# Syntax parsing + static analysis (runs on non-Windows too)
./Invoke-SampleTests.ps1
Configuration values (paths, server names, tenant IDs and so on) are examples. Do not run them as-is in a production environment — adapt them to your own environment.
Related Articles
- Handling Credentials Safely in PowerShell — Banishing Plaintext Passwords from Your Scripts
- The Differences Between Windows PowerShell 5.1 and PowerShell 7 — A Practical Guide to Migrating In-House Scripts
- Automating Excel and CSV Business Tasks with PowerShell — Practical Recipes for Aggregation, Reconciliation and Report Output
- PowerShell Error Handling and Retry Design — From the try/catch Trap to Exit Codes and Retry Best Practices
- Integrating Entra ID Authentication into WinForms/WPF Apps — A Practical Architecture with MSAL.NET and the WAM Broker
- When Task Scheduler Tasks Don’t Run or Exit with 0x1 — Isolating the Cause and Designing for Reliable Operation
Related Consulting Areas
KomuraSoft LLC handles migrating Microsoft 365 operations scripts to Graph, reviewing app registration and permission design, and automating routine work such as inventories and leaver processing.
- Technical Consulting & Design Review
- Legacy Asset Migration & Reuse
- Business System Development
- Contact
References
-
Microsoft Community Hub (Microsoft Entra Blog), Action required: MSOnline and AzureAD PowerShell retirement - 2025 info and resources. On both the MSOnline and AzureAD PowerShell modules being deprecated on 30 March 2024, the MSOnline retirement being carried out in spring 2025 with end of availability on 30 May 2025, AzureAD going out of support on 30 March 2025 and being retired afterwards, and the migration targets being the Microsoft Graph PowerShell SDK and Microsoft Entra PowerShell. ↩ ↩2
-
Microsoft Learn, What is Microsoft Entra PowerShell?. On Microsoft Entra PowerShell being a scenario-oriented module built on the Microsoft Graph PowerShell SDK, its interoperability with the Graph PowerShell SDK’s cmdlets, and its backwards-compatibility options to assist migration from the AzureAD module. The GA announcement is Microsoft Entra PowerShell module now generally available (March 2025). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Install the Microsoft Graph PowerShell SDK. On Microsoft.Graph being a meta-module containing a set of submodules, the ability to install only the submodules you need individually, Microsoft.Graph.Authentication being mandatory for authentication, and the supported PowerShell versions. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Connect-MgGraph. On requesting delegated permissions with -Scopes, app-only authentication with -ClientId / -TenantId / -CertificateThumbprint, checking the current connection information with Get-MgContext, and disconnecting with Disconnect-MgGraph. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Find Microsoft Graph PowerShell commands and permissions. On searching for a command’s module, required permissions and corresponding API with Find-MgGraphCommand, and searching for permission names with Find-MgGraphPermission. ↩ ↩2
-
Microsoft Learn, Use app-only authentication with the Microsoft Graph PowerShell SDK. On the app registration, application permission and admin consent procedure, configuring unattended authentication using a certificate, and the difference between delegated permissions and application permissions. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Paging Microsoft Graph data in your app. On Microsoft Graph responses being paged, specifying -All in the PowerShell SDK to retrieve every page, server-side narrowing with -Filter / -Property (equivalent to $filter and $select), and -ConsistencyLevel eventual with count retrieval for advanced queries. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Microsoft Graph throttling guidance. On per-resource throttling returning HTTP 429, the requirement to wait the number of seconds specified in the response’s Retry-After header before retrying, and the recommendation to design so as to reduce the number of requests in the first place. ↩ ↩2 ↩3
-
Microsoft Learn, signInActivity resource type. On both the AuditLog.Read.All and User.Read.All permissions being required to retrieve a user’s signInActivity property, the tenant licensing requirements, and how lastSignInDateTime represents interactive sign-in attempts (including both successes and failures) while lastSuccessfulSignInDateTime represents successful interactive and non-interactive sign-ins and lastNonInteractiveSignInDateTime represents non-interactive sign-ins. ↩ ↩2
-
Microsoft Learn, Update user (Microsoft Graph API). On the permissions required to update a user being defined per property, with User.EnableDisableAccount.All listed as the least-privileged permission for changing accountEnabled, and the broader User.ReadWrite.All also being able to perform it. Also on delegated scenarios requiring a Microsoft Entra administrator role in addition to the appropriate scope, Privileged Authentication Administrator being the least-privileged role able to update accountEnabled for all administrators in the tenant, an administrator role higher than the target being required in general, and app-only scenarios also requiring a higher administrator role assigned to the app when the target is an administrator. ↩ ↩2 ↩3
-
Microsoft Learn, user: revokeSignInSessions (Microsoft Graph API). On User.RevokeSessions.All being listed as the least-privileged permission for revoking sign-in sessions, admin consent being required, and the invalidation targeting refresh tokens and browser session cookies while already-issued access tokens may remain usable until they expire (with continuous access evaluation being involved in immediate enforcement). ↩ ↩2
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Distributing and Updating PowerShell Modules In-House — PSResourceGet and an Internal Repository
How to move on from copying and reusing ps1 files in a shared folder. Covers how to write a module manifest, versioning, building an inte...
Handling Credentials Safely in PowerShell — Banishing Plaintext Passwords from Your Scripts
A practical walkthrough of moving plaintext passwords out of PowerShell scripts and into safe storage: what SecureString really is and wh...
An Introduction to PowerShell Remoting (WinRM) — Managing Multiple Windows Machines at Once
An introduction to managing multiple Windows machines at once with PowerShell Remoting (WinRM). Covers how it works and ports 5985/5986, ...
PowerShell Execution Policy and Script Signing — A Practical Guide to Graduating From "Papering Over It With Bypass"
PowerShell's execution policy is "a safety feature, not a security boundary." This article covers the differences between policies like R...
Will NTLM Deprecation Stop Your Business Apps? — How to Collect Audit Logs, and the Order in Which to Kill Dependencies
A practical procedure for finding out where your Windows environment and business applications depend on NTLM ahead of its retirement: au...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Frequently Asked Questions
Common questions about the topic of this article.
- Can I no longer use the AzureAD module or the MSOnline module?
- Correct — both have been retired. The MSOnline and AzureAD modules were deprecated on 30 March 2024; MSOnline reached end of availability on 30 May 2025, and AzureAD went out of support on 30 March 2025 and was retired afterwards. Even if you have scripts that still appear to work, they are in a state where they could stop at any time. The migration targets are the Microsoft Graph PowerShell SDK, or the Microsoft Entra PowerShell module built on top of it (GA in March 2025).
- Installing the Microsoft.Graph module is heavy. Can I make it lighter?
- Yes. Microsoft.Graph is a meta-module with a large number of submodules underneath it, so installing the whole thing takes a long time both to install and to load. In practice, the realistic approach is to install only the submodules for the workloads you use: Microsoft.Graph.Users for user management, Microsoft.Graph.Groups for groups, and the mandatory Microsoft.Graph.Authentication for authentication. You can find out which module a given command belongs to with Find-MgGraphCommand.
- I want to run this unattended from Task Scheduler, but I get an interactive sign-in prompt.
- Switch to app-only authentication using an app registration (service principal) and a certificate. Register an app in Microsoft Entra ID, grant admin consent for the required application permissions, then pass -ClientId, -TenantId and -CertificateThumbprint to Connect-MgGraph to connect without any interaction. A certificate is safer than a client secret, and its expiry management is clearer. Place the certificate in the executing account's certificate store, and be sure to establish an operational procedure for renewing it before it expires.
- What should I specify for Connect-MgGraph's -Scopes?
- Specify only the minimum permissions the commands you want to run require. You can check what's needed with Find-MgGraphPermission or the command's documentation; for read-only work, a .Read.-style permission such as User.Read.All is enough. If the purpose is an inventory or an audit, don't request write permissions. Once consent has been granted, it is recorded in the tenant, so "just consent to Directory.ReadWrite.All for now" becomes a future risk. It's safer to separate app registrations by purpose and separate the permissions along with them.
- When I use Get-MgUser against a tenant with a lot of users, I only get part of the list.
- Because Microsoft Graph responses are paged. Adding -All automatically walks all the pages. On top of that, bulk retrieval can be throttled and return a 429 response, so the basics are to narrow to just the fields you need with -Property and to push filtering to the server side with OData (-Filter) rather than client-side Where-Object. If all you want is a count, you can retrieve just the count by combining -ConsistencyLevel eventual with -CountVariable.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links