The AzureAD and MSOnline PowerShell modules are officially retired, yet I still find plenty of legacy scripts in customer environments. Some produce daily compliance reports, others sit deep inside provisioning workflows. Since Microsoft flipped the final retirement switch earlier this year, migrating to the Microsoft Graph PowerShell SDK has been the only real option. That is more work than swapping a few command names, because the SDK expects you to think about automation a little differently.

Old scripts need more than new cmdlet names

The hard part of the migration usually isn't technical complexity. It's the sheer number of old scripts. Most environments I see are full of trusted Get-AzureADUser or Set-MsolUser calls. The Graph SDK brings new cmdlet names, and it also asks you to work at a finer level of detail: authentication goes through Microsoft Graph, and permissions move to the foreground. That feels rough at first. Find-MgGraphCommand and Find-MgGraphPermission have saved me hours when mapping old functionality to its new equivalent, and Microsoft's migration guide is something I keep open the whole time.

Practical tip: Use the migration to review your automations rather than porting old logic one to one. It's a good opportunity to modernize and get rid of some technical debt.

Authentication now revolves around scopes, consent and app-only access

This is where old habits stop working first. A plain Connect-AzureAD is history. Every Connect-MgGraph now involves scopes and explicit consent, which is more secure and forces you to state exactly what your script is allowed to access. Interactive sessions are easy. For unattended jobs I use certificate-based app-only authentication. The setup takes some effort, but afterwards it runs reliably, and interactive prompts no longer break overnight runs.

This is what an app-only connection looks like:

Connect-MgGraph -ClientId $appid -TenantId $tenantid -CertificateThumbprint $thumbprint -Scopes "User.Read.All"

In workshops I use this simple diagram to show how authentication types and scopes fit together:

flowchart TD
    User["Admin/Script"] -->|"Connect-MgGraph (User login)"| Graph["Microsoft Graph"]
    App["Automation/App-only"] -->|"Connect-MgGraph (Certificate)"| Graph
    Graph -->|"Scopes/Permissions"| Resource["M365 resources"]

Figure: How scripts and apps connect through the Microsoft Graph PowerShell SDK

Microsoft also documents the authentication cmdlets in detail.

The -All parameter and filters keep large datasets manageable

If you work with large Microsoft 365 tenants, you'll know that Graph SDK cmdlets return only partial results by default, often 100 objects at a time, unless you add -All. For reports covering every user, group or device, you need it. Still, I try to be selective. Pulling every object can be slow and may run into throttling or other limits, so I usually combine paging with targeted filters to keep things fast and reliable.

A short script lists users without MFA methods

One request I get often is a report of users who have no MFA method configured. With the Graph PowerShell SDK this is fairly simple if you're comfortable with basic scripting. The pattern below is my usual starting point. I run it in a local PowerShell 7 session with the Microsoft.Graph module installed. Keep in mind that Graph filters use OData syntax, not KQL:

Connect-MgGraph -Scopes "User.Read.All","UserAuthenticationMethod.Read.All"
$users = Get-MgUser -All -Filter "accountEnabled eq true" -Property Id,DisplayName,UserPrincipalName
foreach ($user in $users) {
    # The password also counts as a method, so filter it out
    $mfa = Get-MgUserAuthenticationMethod -UserId $user.Id |
        Where-Object { $_.AdditionalProperties['@odata.type'] -ne '#microsoft.graph.passwordAuthenticationMethod' }
    if (-not $mfa) {
        Write-Output ("{0} ({1}) has no MFA methods" -f $user.DisplayName, $user.UserPrincipalName)
    }
}

In large tenants it makes sense to run the queries in parallel or to narrow down the set of users you check.

The migration costs effort but leaves a better toolkit

I won't pretend the migration is always enjoyable, particularly in messy environments. There is a real learning curve, and getting used to scopes and fine-grained permissions takes time. What I do like is the consistency. Nearly everything in M365 can now be scripted with one modern module, from user management to Teams and OneDrive. The SDK also pushes you to rethink processes instead of carrying old baggage along. Once the early frustration fades, you have a toolkit that I think is better than the retired modules. A few things are still clumsy, but a lot of it is clearly well designed, and you notice that by the time you've rewritten your third or fourth script.