vysovsky.com / reference / migration

Deprecated Microsoft 365 Cmdlets and Their Replacements

What stopped working, what replaced it, and the behaviour differences that bite during migration. Written for MSP helpdesk and admin engineers maintaining scripts across many tenants.

Last reviewed June 2026. Covers Exchange Online PowerShell V3, Microsoft Graph PowerShell SDK, and the retired MSOnline and AzureAD modules.

Microsoft has spent the last few years retiring entire layers of its PowerShell tooling: basic-auth remoting into Exchange Online, the RPS-backed cmdlets behind it, the MSOnline and AzureAD modules, and a steady list of individual cmdlets inside Exchange Online itself. Scripts that ran for years now fail with errors that rarely say "this cmdlet was deprecated", which is why a working knowledge of the replacements matters more than the deprecation announcements themselves.

The table below is the quick lookup. The sections after it explain each migration with working examples, because most replacements are not drop-in: parameters change, output shapes change, and a few familiar tricks stop working entirely.

Quick lookup

Type to filter. Matches old and new cmdlet names and notes.

Deprecated / retiredUse insteadNotes
Get-MessageTraceGet-MessageTraceV2V2 reaches back 90 days. No -Page parameter and no -Subject parameter; see the message trace section.
Get-MessageTraceDetailGet-MessageTraceDetailV2Pairs with Get-MessageTraceV2 output.
Search-AdminAuditLogSearch-UnifiedAuditLogAdmin audit events now live in the unified audit log.
Search-MailboxAuditLogSearch-UnifiedAuditLogUse -RecordType ExchangeItem and related record types.
Get-MailboxGet-EXOMailboxREST-backed, faster at scale. Returns a reduced default property set; request more with -Properties or -PropertySets.
Get-MailboxStatisticsGet-EXOMailboxStatisticsSame idea: faster, leaner default output.
Get-MailboxFolderStatisticsGet-EXOMailboxFolderStatistics
Get-MailboxPermissionGet-EXOMailboxPermission
Get-MailboxFolderPermissionGet-EXOMailboxFolderPermission
Get-RecipientPermissionGet-EXORecipientPermission
Get-RecipientGet-EXORecipient
Get-CASMailboxGet-EXOCASMailbox
Get-MobileDeviceStatisticsGet-EXOMobileDeviceStatisticsThe old cmdlet never supported -ResultSize Unlimited, despite what old forum posts claim.
New-PSSession to outlook.office365.comConnect-ExchangeOnlineBasic-auth WinRM remoting is gone. EXO V3 module connects over REST.
Connect-EXOPSSessionConnect-ExchangeOnlineV1 module helper, long retired.
Connect-MsolServiceConnect-MgGraphMSOnline module is retired. Request scopes explicitly at connect time.
Connect-AzureADConnect-MgGraphAzureAD module is retired.
Get-MsolUserGet-MgUserGraph returns only requested properties; add -Property. Paging needs -All.
Get-AzureADUserGet-MgUser
Set-MsolUserUpdate-MgUser
Set-AzureADUserUpdate-MgUser
New-MsolUserNew-MgUserPassword goes in a -PasswordProfile hashtable.
Remove-MsolUserRemove-MgUserSoft delete with a 30-day recovery window.
Set-MsolUserPasswordUpdate-MgUser -PasswordProfileNeeds an appropriate admin role; least-privileged scope is User-PasswordProfile.ReadWrite.All.
Set-MsolUserLicenseSet-MgUserLicenseTakes SkuId GUIDs via -AddLicenses / -RemoveLicenses hashtables.
Get-MsolAccountSkuGet-MgSubscribedSku
Get-MsolGroupGet-MgGroup
Get-AzureADGroupGet-MgGroup
Add-MsolGroupMemberNew-MgGroupMemberTakes object IDs, not UPNs. Resolve the user first.
Add-AzureADGroupMemberNew-MgGroupMember
Remove-AzureADGroupMemberRemove-MgGroupMemberByRef
Get-MsolRole / Get-AzureADDirectoryRoleGet-MgDirectoryRoleShows activated roles only; Get-MgRoleManagementDirectoryRoleDefinition lists all.
Get-MsolRoleMemberGet-MgDirectoryRoleMemberMember details sit in AdditionalProperties.
Get-AzureADAuditSignInLogsGet-MgAuditLogSignInRequires AuditLog.Read.All and Entra ID P1 for full retention.
Get-AzureADAuditDirectoryLogsGet-MgAuditLogDirectoryAudit
Revoke-AzureADUserAllRefreshTokenRevoke-MgUserSignInSession
Get-MsolDevice / Get-AzureADDeviceGet-MgDevice
Get-MsolDomain / Get-AzureADDomainGet-MgDomain
Get-MsolCompanyInformationGet-MgOrganizationOnPremisesSyncEnabled lives here for hybrid checks.

Message trace: Get-MessageTrace to Get-MessageTraceV2

The original Get-MessageTrace and Get-MessageTraceDetail cmdlets have been deprecated in favour of V2 versions. The headline improvement is history: V2 can query up to 90 days of message data, where V1 stopped at 10. But two habits from V1 no longer work, and both fail in confusing ways.

There is no -Subject parameter

V1 scripts sometimes filtered by subject server-side. Get-MessageTraceV2 does not support a subject parameter, so filter client-side with Where-Object instead:

Old habit (fails on V2)

Get-MessageTrace -SenderAddress user@contoso.com -Subject "Invoice"

Working V2 pattern

Get-MessageTraceV2 -SenderAddress user@contoso.com -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) |
  Where-Object Subject -like "*Invoice*" |
  Select-Object Received, SenderAddress, RecipientAddress, Subject, Status

Paging works differently

V1 used -Page and -PageSize to walk through large result sets. V2 drops page-based paging; for result sets that hit the per-query cap, narrow the time window and walk through it in slices (for example, day by day) rather than requesting page 2. For one-off investigations this rarely matters, but scheduled reporting scripts built around a page loop need restructuring.

Detail lookups pair with V2

Get-MessageTraceV2 -RecipientAddress user@contoso.com -StartDate (Get-Date).AddDays(-2) -EndDate (Get-Date) |
  Get-MessageTraceDetailV2 |
  Select-Object Date, Event, Detail

Audit logs: Search-AdminAuditLog to Search-UnifiedAuditLog

Search-AdminAuditLog and Search-MailboxAuditLog are deprecated. Both kinds of event now live in the unified audit log, searched with Search-UnifiedAuditLog or through the Purview portal.

Deprecated

Search-AdminAuditLog -Cmdlets Set-Mailbox -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date)

Replacement

Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
  -RecordType ExchangeAdmin -Operations "Set-Mailbox" -ResultSize 1000 |
  Select-Object CreationDate, UserIds, Operations

The unified log wraps event detail in a JSON blob in the AuditData property. To get at the interesting fields, parse it:

Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-1) -EndDate (Get-Date) -RecordType ExchangeAdmin -ResultSize 500 |
  ForEach-Object { $_.AuditData | ConvertFrom-Json } |
  Select-Object CreationTime, UserId, Operation, ObjectId
Audit log searches return at most 5000 results per call in default mode. For large investigations, use -SessionId with -SessionCommand ReturnLargeSet and loop until no more results come back.

The Get-EXO* cmdlets: same data, different defaults

The Exchange Online V3 module ships REST-backed versions of the highest-volume read cmdlets, prefixed EXO: Get-EXOMailbox, Get-EXOMailboxStatistics, Get-EXORecipient, Get-EXOCASMailbox, Get-EXOMobileDeviceStatistics and the permission cmdlets. The originals still exist, but the EXO versions are markedly faster on bulk operations and are the right default for anything touching more than a handful of mailboxes.

The trade-off is that EXO cmdlets return a minimal property set by default. A script that did Get-Mailbox | Select ForwardingSmtpAddress will get blank output from Get-EXOMailbox until the property is requested explicitly:

Requesting properties explicitly

Get-EXOMailbox -ResultSize Unlimited -Properties ForwardingSmtpAddress, HiddenFromAddressListsEnabled |
  Where-Object ForwardingSmtpAddress -ne $null |
  Select-Object DisplayName, ForwardingSmtpAddress

Property groups are also available via -PropertySets (for example Minimum, Delivery, Quota) when you need a themed bundle rather than individual fields.

Blank columns after switching to an EXO cmdlet are almost never a data problem. Check -Properties first.

Connecting: remoting sessions to Connect-ExchangeOnline

Older scripts connected to Exchange Online by creating a WinRM session against outlook.office365.com with basic authentication, then importing it. Basic authentication for Exchange Online PowerShell has been switched off, so this pattern is dead, as is the V1 Connect-EXOPSSession helper and the V3 module's temporary -UseRPSSession escape hatch.

Dead pattern

$session = New-PSSession -ConfigurationName Microsoft.Exchange `
  -ConnectionUri https://outlook.office365.com/powershell-liveid/ `
  -Credential $cred -Authentication Basic -AllowRedirection
Import-PSSession $session

Current pattern

Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
# unattended automation: certificate-based app-only auth
Connect-ExchangeOnline -AppId $appId -CertificateThumbprint $thumb -Organization contoso.onmicrosoft.com

For unattended scripts, certificate-based app-only authentication replaces stored credentials. Storing a password for a basic-auth session was always the weakest link in MSP automation; there is no supported way to keep doing it.

MSOnline and AzureAD to Microsoft Graph PowerShell

The MSOnline (*-Msol*) and AzureAD (*-AzureAD*) modules are retired. Scripts still calling them fail at connect time, so this is the migration most likely to surface as a sudden outage in old onboarding and reporting scripts. The replacement for both is the Microsoft Graph PowerShell SDK, and it is not a rename: Graph behaves differently in five ways that account for nearly every migration bug.

1. You must ask for properties

MSOnline returned everything about a user by default. Graph returns a small default set, and anything else comes back empty unless requested with -Property:

Silently returns blank Department

Get-MgUser -UserId user@contoso.com | Select-Object DisplayName, Department

Correct

Get-MgUser -UserId user@contoso.com -Property DisplayName, Department |
  Select-Object DisplayName, Department

2. You must ask for all pages

Graph list cmdlets return one page (typically 100 objects) unless -All is supplied. A migrated user report that mysteriously caps at 100 rows has lost its -All.

3. Advanced filters need two extra parameters

Filters that count, negate or use certain operators are "advanced queries" and require -ConsistencyLevel eventual together with -CountVariable. Without them, Graph rejects the request:

Get-MgUser -All -Filter "assignedLicenses/`$count eq 0 and userType eq 'Member'" `
  -ConsistencyLevel eventual -CountVariable c

4. Relationships use object IDs, not UPNs

Membership and reference operations take directory object IDs. The reliable pattern is resolve first, then act:

$grp = Get-MgGroup -Filter "displayName eq 'All Staff'"
$usr = Get-MgUser -UserId "user@contoso.com"
New-MgGroupMember -GroupId $grp.Id -DirectoryObjectId $usr.Id

5. Related objects hide in AdditionalProperties

Cmdlets that return generic directory objects (group members, role members, a user's manager) put the useful fields inside AdditionalProperties:

Get-MgGroupMember -GroupId $grp.Id -All |
  ForEach-Object { [pscustomobject]@{
    Name = $_.AdditionalProperties.displayName
    UPN  = $_.AdditionalProperties.userPrincipalName } }
Scopes are the other recurring surprise. Connect-MgGraph grants only the scopes requested at connect time, so a script that worked in your window can fail in a colleague's session with "Insufficient privileges". Put the Connect-MgGraph line with explicit scopes at the top of every script.

For ready-made Graph commands covering users, licensing, groups, roles, sign-in logs and MFA methods, see the Entra ID command builder on this site. Every command there lists the scopes it needs.

Cmdlets that do not exist

A special category: cmdlets that circulate in forum answers and AI-generated scripts but have never existed. They fail with "not recognized as the name of a cmdlet", which sends people hunting for module problems that are not there.

Not a real cmdletWhat people actually want
Get-DmarcAggregateReportThere is no Exchange Online cmdlet for DMARC reports. To check a DMARC record: Resolve-DnsName -Name "_dmarc.contoso.com" -Type TXT. Aggregate reports arrive by email to the address in the record's rua tag.
Get-MobileDeviceStatistics -ResultSize UnlimitedThe cmdlet exists but the parameter does not. Use Get-EXOMobileDeviceStatistics -Mailbox user@contoso.com per mailbox.
Get-MailboxQuotaQuotas are properties, not a cmdlet: Get-Mailbox user@contoso.com | Format-List *Quota*.

When in doubt, verify before debugging: Get-Command Get-MessageTraceV2 confirms a cmdlet exists in your session, and Get-Command Get-MessageTraceV2 -Syntax shows its real parameters. Thirty seconds of verification routinely saves an hour of chasing a cmdlet that was never there.

Migration checklist for old scripts

When reviving a script of unknown age, work through it in this order. First, replace the connection block: Connect-ExchangeOnline for Exchange, Connect-MgGraph with explicit scopes for directory work. Second, run the script body through a search for Msol, AzureAD, Get-MessageTrace and Search-AdminAuditLog, and swap in the replacements from the table above. Third, on every Graph list call, confirm -All and -Property are present. Fourth, test against a single known object before running at tenant scale. The Exchange and Entra ID builders on this site produce current-syntax commands you can diff your old script against.