Key Takeaways
- Understand how modern Exchange Online PowerShell differs from legacy Remote PowerShell.
- Distinguish connection state, authentication, authorization, and command routing.
- Choose between standard REST-backed cmdlets and optimized Get-EXO* retrieval commands.
- Treat properties, object types, paging, filtering, and retries as part of workload design.
- Migrate RPS scripts by redesigning assumptions and validating the result.
Inside Modern Exchange Online PowerShell: REST, Performance, Objects, and Migration
Modern Exchange Online PowerShell looks familiar.
That is intentional.
You still connect with Connect-ExchangeOnline. You still run familiar Exchange cmdlets. You still receive PowerShell objects and send them through pipelines.
But underneath that familiar surface, the architecture has changed.
Modern Exchange Online PowerShell uses a token-authenticated, REST-backed command model with locally tracked connection metadata. It is not a traditional PowerShell remoting session.
That distinction changes several assumptions that older Exchange scripts quietly relied on.
Get-PSSession is no longer the right way to inspect an Exchange Online connection.
Invoke-Command cannot target an Exchange Online REST connection because there is no remote PowerShell runspace behind it.
The optimized Get-EXO* family deliberately controls which properties are returned.
And performance is not a property of a cmdlet name alone. It depends on the tenant, query, host, module version, PowerShell runtime, connection configuration, and service conditions.
This article looks beneath the familiar cmdlet surface so that existing scripts can be modernized deliberately instead of being rewritten by simple search-and-replace.
1. From Remote PowerShell to REST
The model that came before
Legacy Exchange Online administration was based on PowerShell remoting.
The client created a PSSession, imported commands, and sent them to a remote runspace. Returned data crossed a serialization boundary, which often produced Deserialized.* objects and removed methods that existed on the original server-side objects.
That model also brought session lifecycle, WinRM configuration, remoting quotas, remote runspace state, and connection recovery into the script’s design.
Get-PSSession
Invoke-Command -Session $Session -ScriptBlock {
Get-Mailbox -ResultSize 10
}
The modern model
Modern Exchange Online PowerShell replaces that remoting path with REST-backed administrative requests.
A useful mental model is:
PowerShell process → ExchangeOnlineManagement → REST-backed Exchange request → Exchange service → PowerShell object
There is no Exchange Online PSSession representing that connection.
So this:
Get-PSSession
should not be your connection-health test.
Use:
Get-ConnectionInformation
And this:
Invoke-Command
does not become the REST equivalent of remote execution. There is no remote Exchange PowerShell runspace for it to target.
Why the change matters
A script migrated from RPS may still contain assumptions about:
- persistent remote sessions
- remote script blocks
Get-PSSessionInvoke-Command- serialized objects
- old RPS-specific switches
- broad implicit property availability
Modernization therefore requires more than replacing one connection command with another.
2. Understanding Connect-ExchangeOnline
A beginner can reasonably look at:
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
and think, “I connected to a remote PowerShell session.”
That is no longer the best way to think about it.
The connection process establishes authentication and Exchange context, creates local connection metadata, imports the authorized command surface, and prepares the process to invoke Exchange operations through the modern service path.
Inspect that state with:
Get-ConnectionInformation |
Select-Object ConnectionId,
State,
TenantID,
UserPrincipalName,
TokenStatus,
TokenExpiryTime,
ModulePrefix,
PageSize
The exact information exposed can vary by module version, so applications should inspect the installed object rather than assume that every version produces an identical schema.
Authentication is only the first gate
A successful connection proves that authentication succeeded.
It does not prove that every Exchange operation is authorized.
A later failure can come from:
- Exchange RBAC
- parameter authorization
- tenant routing
- backend execution
- paging
- local object conversion
That distinction becomes especially important in unattended automation.
Modern authentication patterns
Interactive administration might use:
Connect-ExchangeOnline `
-UserPrincipalName admin@contoso.com `
-ShowBanner:$false
Supported unattended scenarios can use certificate-based app-only authentication or managed identity:
Connect-ExchangeOnline `
-AppId $AppId `
-CertificateThumbprint $Thumbprint `
-Organization 'contoso.onmicrosoft.com' `
-ShowBanner:$false
Connect-ExchangeOnline `
-ManagedIdentity `
-Organization 'contoso.onmicrosoft.com' `
-ShowBanner:$false
The important engineering point is that authentication method, Exchange authorization, and workload identity lifecycle are separate concerns.
3. Standard Cmdlets and Get-EXO* Have Different Jobs
Modernization often starts with a tempting replacement:
Get-Mailbox
becomes:
Get-EXOMailbox
That is not enough.
The two command families serve different purposes.
Standard REST-backed cmdlets
These preserve much of the familiar Exchange administrative surface.
They can be the better choice when you need a parameter or operation that the optimized family does not provide, or when the existing workflow depends on broader administrative behavior.
Optimized Get-EXO* cmdlets
The optimized family is focused on retrieval, especially bulk retrieval.
Examples include:
Get-EXOMailbox
Get-EXORecipient
Get-EXOCasMailbox
Get-EXOMailboxPermission
Get-EXORecipientPermission
Get-EXOMailboxStatistics
Get-EXOMailboxFolderStatistics
Get-EXOMailboxFolderPermission
The difference is not cosmetic. The optimized family gives you more deliberate control over properties, paging, and retrieval behavior.
| Need | Standard REST-backed cmdlet | Get-EXO* optimized cmdlet |
|---|---|---|
| Administrative breadth | Broad, familiar Exchange administration surface | Primarily optimized for retrieval |
| High-volume reads | Possible, but retrieval cost should be measured | Designed with bulk retrieval and paging in mind |
| Property control | Broader/traditional output may be available | Minimum schema plus `-Properties` / `-PropertySets` |
| Migration | Useful when existing semantics must be preserved | Useful when a reporting or inventory workload can adopt an explicit schema |
The right question is:
Which supported command gives this workload the behavior and data contract it actually needs?
4. The Property Model Is Part of the Query
This is one of the most important differences for anyone moving from older Exchange scripts.
Consider:
$mailbox = Get-EXOMailbox -Identity alex@contoso.com
Later:
$mailbox.Department
The second statement does not cause Exchange to fetch Department.
If you need it, request it:
Get-EXOMailbox `
-Identity alex@contoso.com `
-Properties Department,Office
Or use a documented property set:
Get-EXOMailbox `
-Identity alex@contoso.com `
-PropertySets Archive
-Properties and -PropertySets influence retrieval. Select-Object only works with properties already present in the returned object.
$m = Get-EXOMailbox `
-ResultSize 1 `
-Properties Department,Office
$hasDepartment = $null -ne $m.PSObject.Properties['Department']
$valueIsNull = $null -eq $m.Department
Why property breadth matters
A requested property can affect:
- service-side processing
- response size
- network transfer
- object materialization
- PowerShell memory
- serialization or export
A small scalar and a large multivalued property can therefore have very different consequences.
Request the properties the workload needs, use documented property sets when they genuinely fit, and avoid -PropertySets All as a convenience shortcut.
5. Filtering and Paging at Scale
Filtering is where retrieval architecture starts to become a performance issue.
A server-side filter:
Get-EXOMailbox `
-Filter "Department -eq 'Research'" `
-Properties Department
allows the service to evaluate the predicate when the cmdlet and property support it.
A client-side filter:
Get-EXOMailbox `
-ResultSize Unlimited `
-Properties Department |
Where-Object Department -eq 'Research'
retrieves a broader population before filtering locally.
That does not mean server-side filtering is universally faster.
Supported operators, property behavior, selectivity, tenant size, module version, and service conditions all matter.
-ResultSize Unlimited
Consider:
Get-EXOMailbox -ResultSize Unlimited
Unlimited removes the result-count limit.
It does not mean:
- one response
- no pagination
- no throttling
- unlimited memory
Exchange Online retrieval is paged.
You can inspect page size:
Get-ConnectionInformation |
Select-Object ConnectionId,PageSize,State
and configure it when connecting:
Connect-ExchangeOnline `
-PageSize 1000 `
-ShowBanner:$false
Larger pages may reduce request overhead while increasing latency or memory pressure. Smaller pages can make progress more incremental while increasing request count.
Treat page size as a tuning variable.
Pipelines are not automatically memory-light
A pipeline can look streaming while later stages retain large portions of the result.
Sort-Object, Group-Object, joins, assignment to variables, exports, and other consumers can buffer data.
A script that behaves perfectly with a few hundred objects can behave very differently when the result set grows by two orders of magnitude.
6. REST Changed the Serialization Boundary, Not the Need for Type Testing
Legacy RPS exposed serialization through Deserialized.* objects.
Modern REST-backed PowerShell removes that specific PowerShell-remoting boundary, but the module still converts service data into PowerShell objects.
Scripts should therefore continue to care about:
- PSTypeNames
- CLR types
- null semantics
- arrays and collections
- multivalued properties
- nested values
- dates
- quotas
- export behavior
A value that looks identical in the console can still behave differently in code.
Inspect the object:
$m = Get-EXOMailbox `
-ResultSize 1 `
-Properties Department,Office
$m.PSTypeNames
$m.PSObject.Properties |
Sort-Object Name |
Select-Object Name,
TypeNameOfValue,
IsGettable,
IsSettable
Create an application-owned schema
Rather than allowing raw Exchange objects to become your application’s contract:
Get-EXOMailbox `
-ResultSize Unlimited `
-Properties Department,Office |
ForEach-Object {
[pscustomobject]@{
ExternalDirectoryObjectId = [string]$_.ExternalDirectoryObjectId
UserPrincipalName = [string]$_.UserPrincipalName
PrimarySmtpAddress = [string]$_.PrimarySmtpAddress
Department = [string]$_.Department
Office = [string]$_.Office
EmailAddresses = [string[]]$_.EmailAddresses
}
}
Now the application depends on a schema you control.
That creates a useful boundary between Exchange’s representation and your application’s data contract.
7. Errors and Retries Need More Than a catch Block
A fragile script often starts by matching exception text:
catch {
if ($_.Exception.Message -match 'does not exist') {
# ...
}
}
Human-readable messages are useful to people, but they are a poor foundation for application logic.
Capture structured error information instead:
try {
Set-Mailbox `
-Identity $Identity `
-Office $Office `
-ErrorAction Stop
}
catch {
[pscustomobject]@{
Identity = $Identity
ExceptionType = $_.Exception.GetType().FullName
ErrorId = $_.FullyQualifiedErrorId
Category = $_.CategoryInfo.Category
Target = $_.TargetObject
Message = $_.Exception.Message
}
}
Modern Exchange Online PowerShell also provides built-in handling for some transient failures.
That is useful, but application retry still needs a policy.
A safe retry design should consider:
- what failed
- whether the operation can safely be repeated
- whether throttling is occurring
- how many attempts are acceptable
- how much delay to introduce
- whether a write may already have succeeded
- how the final state will be verified
Reads are usually easier to retry than writes. A lost response after a successful write is precisely the case where blindly repeating the operation can be unsafe.
8. Multiple Connections Require Explicit Routing
A single connection is easy to understand.
Multiple tenants or identities are not.
Inspect:
Get-ConnectionInformation
and pay attention to:
- TenantID
- ConnectionId
- ModulePrefix
- State
- ConnectionUsedForInbuiltCmdlets
For standard commands, explicit prefixes can help separate tenants.
For optimized Get-EXO* operations, validate the intended connection before the relevant phase.
For independent or high-value workloads, a separate PowerShell process can sometimes be simpler than maintaining complex shared connection state.
The goal is straightforward:
Make it difficult for a valid Exchange command to reach the wrong tenant.
9. -CommandName and Session Memory
A large automation job may need only a small portion of the Exchange command surface.
You can limit imported commands:
$needed = @(
'Get-Mailbox',
'Set-Mailbox',
'Get-Recipient'
)
Connect-ExchangeOnline `
-CommandName $needed `
-SkipLoadingFormatData `
-ShowBanner:$false
This can reduce command-import and memory overhead.
But command discovery should happen before narrowing the command set. Dynamic command construction can make static analysis incomplete.
Likewise, disconnecting does not guarantee that every allocated byte immediately disappears from the process.
For serious memory benchmarking, process isolation gives much cleaner results.
10. Runtime and Module Version Are Part of the Script
The Exchange cmdlet is only one component of the execution environment.
Compatibility can depend on:
- ExchangeOnlineManagement
- Windows PowerShell vs. PowerShell 7
- .NET
- operating system
- authentication flow
Capture that environment:
$module = Get-Module ExchangeOnlineManagement -ListAvailable |
Sort-Object Version -Descending |
Select-Object -First 1
[pscustomobject]@{
PowerShell = $PSVersionTable.PSVersion.ToString()
Edition = $PSVersionTable.PSEdition
OS = $PSVersionTable.OS
EXOModule = $module.Version.ToString()
}
For production automation, use controlled module versions, upgrade canaries, regression tests, and a rollback path rather than blindly accepting every module update.
11. Measure Performance Like an Engineer
Performance discussions often become unreliable because the experiment is too small.
For example:
Measure-Command {
Get-EXOMailbox -ResultSize Unlimited
}
and:
Measure-Command {
Get-EXOMailbox `
-ResultSize Unlimited `
-Properties Department,Office
}
Running each once does not establish a general rule.
The benchmark model should instead repeat the tests, alternate execution order, record the environment, and check correctness alongside timing.
Useful measurements include:
- elapsed-time distribution
- object count
- property count
- memory
- serialized sample-size proxy
- errors
- failures
- module and PowerShell versions
Performance laboratory
Measure the workload, not the assumption
Use this guide to compare valid Exchange Online PowerShell retrieval patterns under controlled conditions. It is a laboratory model, not a claim that one query is universally faster.
Keep the comparison fair
Control the environment
Keep tenant data, host, PowerShell, ExchangeOnlineManagement version, authentication, connection settings, and query conditions consistent.
Warm the workload
Run a small warm-up when the experiment is intended to measure steady-state retrieval rather than first-use overhead.
Alternate the paths
Run A/B and B/A rather than letting one pattern always run first.
Repeat the trials
Use multiple runs so a single slow or fast result does not become the conclusion.
Capture context
Record timestamps, module/runtime versions, connection configuration, errors, throttling, and observable service conditions.
Compare distributions
Use median and percentile behavior where the sample count supports it; investigate outliers instead of silently deleting them.
What are you comparing?
What this run tells you
Measure the optimized retrieval using its documented minimum property set.
Validate
- Record the number of returned objects.
- Inspect the first object and a representative sample.
- Retain the output when the downstream workload normally consumes it.
Measure
What this run tells you
Measure the same retrieval after explicitly requesting additional properties.
Validate
- Confirm Department and Office are actually present.
- Compare the returned population with Path A.
- Measure the same metrics under the same environment.
Measure
When the numbers arrive
Start with correctness. Then compare the distribution of results and the operational cost around them. Keep the conclusion tied to the tenant, test window, runtime, module, connection, and query that produced it.
Reproduce the benchmark
Download the benchmark harness
A small benchmark can begin with:
$tests = [ordered]@{
Minimum = {
Get-EXOMailbox -ResultSize Unlimited
}
DepartmentOffice = {
Get-EXOMailbox `
-ResultSize Unlimited `
-Properties Department,Office
}
}
Then repeat each pattern, alternate the order, and retain the results.
Correctness must be measured alongside speed.
A faster query that returns the wrong population, misses properties, creates duplicates, or produces incompatible types is not an optimization.
12. Migrating RPS Scripts
This is where the earlier sections come together.
A migration should begin with an inventory of old assumptions.
Search for:
New-PSSession
Import-PSSession
Connect-EXOPSSession
Get-PSSession
Remove-PSSession
Invoke-Command
-UseRPSSession
WSMan configuration
Then inspect the script itself.
Look for:
- cmdlets
- parameter sets
- filters
- implicit defaults
- output properties
- object methods
- formatting
- exports
- error-message parsing
- authentication
- parallelism
- retry behavior
- multitenant routing
The point is to discover what the old architecture allowed the script to assume.
Modernization field guide
Where are you starting from?
Choose the situation that looks most like your workload. The guide focuses on the decisions that matter when moving toward a modern, supportable Exchange Online PowerShell design.
Are you moving an older Remote PowerShell workflow?
Start by finding the assumptions that only made sense when Exchange Online used a remote PowerShell session.
Find the RPS assumptions
Inventory the old connection and execution model before changing commands.
- Search for New-PSSession, Import-PSSession, Connect-EXOPSSession, Get-PSSession, Remove-PSSession, and Invoke-Command.
- Look for -UseRPSSession and WSMan configuration.
- Record filters, parameters, properties, formatting, exports, retry loops, and error-text parsing.
Move control into the local process
Replace remote script blocks with direct Exchange Online cmdlet calls and an explicit connection model.
- Use Connect-ExchangeOnline and inspect the resulting connection with Get-ConnectionInformation.
- Remove assumptions about a remote runspace or PSSession.
- Revisit batching, request boundaries, and N+1 queries created by the old script.
Make the output yours
Do not let the Exchange module's object shape become the application's contract.
- Request every property consumed by the script.
- Normalize Exchange results into an application-owned PSCustomObject or typed schema.
- Regression-test types, nulls, collections, identifiers, and exports.
Validate before declaring success
A script that runs is not necessarily a script that migrated correctly.
- Compare expected populations and stable identifiers.
- Test error handling, resilience, performance, and security.
- Run a canary workload before broader promotion.
Is the script already using modern Exchange Online PowerShell?
Modern does not automatically mean efficient or type-safe. Inspect the retrieval contract before changing the implementation.
Understand what the script actually retrieves
Look for properties that are used later but are never explicitly requested.
- Compare Get-EXO* usage with every downstream property reference.
- Inspect PSObject.Properties and PSTypeNames on representative objects.
- Identify broad property sets, client-side filters, buffering, and repeated lookups.
Tighten the retrieval contract
Ask Exchange for the data the workload needs, and no more.
- Use -Properties for specific additions.
- Use documented -PropertySets when the grouping matches the workload.
- Separate Exchange retrieval objects from published report or application objects.
Test the real pipeline
Measure the complete workload rather than only the first cmdlet in it.
- Compare server-side and client-side filtering where both are supported.
- Include sorting, grouping, joins, exports, and other buffering stages.
- Record timing, object counts, memory, errors, and throttling.
Are you designing the workload from scratch?
Build the connection, schema, error, and verification boundaries before the first production run.
Define the workload contract
Decide what the automation needs to read or change before writing the execution code.
- Choose interactive, certificate-based app-only, managed identity, or another supported authentication pattern.
- Define the target population and required properties.
- Decide what the postcondition should look like.
Make failure observable
Treat errors, retries, and partial progress as normal engineering concerns.
- Capture structured ErrorRecord information.
- Use bounded retry for appropriate transient failures.
- Make writes idempotent where possible and verify the postcondition.
Leave evidence behind
A production job should explain what it did without exposing secrets.
- Log tenant, identity, runtime, module version, and connection metadata.
- Keep baseline, result, and verification artifacts.
- Avoid access tokens, private keys, and full authorization headers in logs.
Is the workload primarily a large read or inventory job?
Optimize the retrieval path, but keep correctness and schema compatibility ahead of a stopwatch result.
Ask for the right data
Use Get-EXO* deliberately and request only the properties the report consumes.
- Avoid -PropertySets All unless the workload genuinely needs it.
- Do not use Select-Object as a substitute for retrieval.
- Keep the report schema explicit and stable.
Control volume and memory
Large datasets can be affected by paging, buffering, and downstream consumers.
- Treat -ResultSize Unlimited as an output-count setting, not infinite execution.
- Inspect page size and client-side buffering.
- Avoid N+1 remote lookups; retrieve a usable dataset and work locally.
Measure distributions
One fast run is not enough to establish a performance rule.
- Warm up the workload and alternate test order.
- Repeat trials under controlled environment conditions.
- Measure elapsed time, object count, property count, memory, size proxy, errors, and failures.
Will one process work with more than one Exchange Online tenant?
Make connection identity and command routing explicit so a valid command cannot silently reach the wrong tenant.
Know every active connection
Connection state becomes part of your application state when several tenants are involved.
- Inspect Get-ConnectionInformation before each tenant-specific phase.
- Record TenantID, ConnectionId, ModulePrefix, and relevant connection state.
- Do not infer routing from Get-PSSession.
Use explicit tenant boundaries
Prefer predictable routing over clever connection juggling.
- Use prefixes for standard commands when appropriate.
- For Get-EXO* operations, validate the intended built-in-cmdlet connection.
- Consider separate PowerShell processes when tenant isolation is more important than process reuse.
Prove the destination
A successful command can still be operationally wrong if it executed against the wrong tenant.
- Validate tenant identity before high-impact phases.
- Keep per-tenant evidence and output paths separate.
- Make tenant routing part of the job log and failure diagnostics.
Put the migration plan into practice
Download the RPS → REST migration checklist
Download the modernization worksheet
Replace the connection model
A modern connection helper can validate its resulting connection:
function Connect-WorkloadExchange {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Organization,
[Parameter(Mandatory)]
[string[]]$CommandName
)
Connect-ExchangeOnline `
-Organization $Organization `
-ManagedIdentity `
-CommandName $CommandName `
-ShowBanner:$false `
-ErrorAction Stop
$connection = Get-ConnectionInformation |
Where-Object ConnectionUsedForInbuiltCmdlets |
Select-Object -First 1
if (-not $connection -or
$connection.State -notmatch 'Connected') {
throw 'Exchange Online connection validation failed.'
}
$connection
}
The exact State values should be validated against the installed module rather than treated as a permanent undocumented contract.
Replace remote script blocks
A legacy pattern like:
Invoke-Command -Session $Session -ScriptBlock {
Get-Mailbox ...
Get-Recipient ...
}
should be redesigned as local orchestration.
That means revisiting batching, request boundaries, N+1 queries, checkpointing, retries, and write verification.
Normalize the output
Do not let the Exchange module’s raw object shape become the long-term contract of your application.
Define the fields you actually need.
Request them explicitly.
Normalize them.
Test them.
Then let the rest of the application depend on that stable representation.
13. Migration Is Not Complete Until It Passes Validation
A migrated script should satisfy more than:
“The commands ran.”
| Area | What to prove |
|---|---|
| Function | Required commands, parameters, scopes, and recipient types behave correctly. |
| Data | Object counts and stable identifiers match the expected population. |
| Schema | Required properties exist and null/collection semantics are understood. |
| Types | Important runtime types and exports satisfy the application's contract. |
| Errors | Failures are captured and classified through structured error information. |
| Resilience | Retry and write-postcondition behavior has been tested. |
| Performance | Observed performance distribution is acceptable under representative conditions. |
| Security | Identity, Exchange authorization, and secret handling follow the intended design. |
| Operations | Runtime/module versions, upgrade testing, and rollback are documented. |
That is much closer to what “migration complete” should mean.
14. The Patterns Worth Keeping
By now, the modern Exchange Online PowerShell model can be reduced to a handful of habits.
Inspect the connection instead of guessing at it.
Choose the command family deliberately.
Request the properties the workload actually consumes.
Treat object types as part of the application contract.
Filter with both correctness and performance in mind.
Do not confuse Unlimited with unbounded execution.
Treat retries as an engineering decision.
Make tenant routing explicit.
Control runtime and module dependencies.
Measure performance rather than repeating folklore.
Migrate old assumptions, not just old command names.
15. Troubleshooting the Modern Model
When a connection fails, start with the environment:
$PSVersionTable
Get-Module ExchangeOnlineManagement -ListAvailable |
Sort-Object Version -Descending |
Select-Object -First 1 Name,Version
Get-ConnectionInformation
Then separate the failure into the appropriate layer.
Authentication problem
Investigate the chosen authentication flow, system time, browser/WAM prerequisites, certificate access, managed-identity availability, proxy behavior, TLS inspection, or cloud endpoint requirements.
Wrong tenant or wrong connection
Compare:
- TenantID
- identity
- ModulePrefix
- ConnectionId
- ConnectionUsedForInbuiltCmdlets
Do not infer REST routing from Get-PSSession.
Slow retrieval
Record:
- object count
- requested properties
- filter type
- page size
- sorting/grouping
- export behavior
- memory
- retry/throttling
- number of remote calls
Then benchmark again under controlled conditions.
Missing or changed properties
Inspect:
$m.PSObject.Properties
$m.PSTypeNames
Determine whether the property is:
- not retrieved
- null
- empty
- a collection
- a different runtime type
- different for the recipient subtype
- different in the newer module version
That is much more useful than comparing formatted console output.
Conclusion
Modern Exchange Online PowerShell still looks like PowerShell.
That familiar surface is useful, but it can hide a very different execution model underneath.
A REST-backed connection is not a PSSession.
Invoke-Command is not its replacement.
Get-ConnectionInformation is the connection-state view.
Get-EXO* is not merely a renamed Get-Mailbox.
Select-Object cannot retrieve a property that was never returned.
-ResultSize Unlimited does not eliminate paging, throttling, or memory constraints.
A successful command does not automatically prove a compatible result.
A retry does not automatically make a write safe.
And one benchmark run does not establish a universal performance truth.
The practical discipline is straightforward:
Understand the connection, choose the right retrieval model, request the data you need, test the object contract, measure the workload, and migrate old assumptions—not just old command names.
That is what turns the modern Exchange Online PowerShell model from something merely familiar into something you can engineer with confidence.
Downloadable Resources
- Download the benchmark harness
- Download the RPS → REST migration checklist
- Download the modernization worksheet
- Download the modernization glossary
References
- Exchange Online PowerShell documentation
Microsoft documentation for Exchange Online PowerShell, supported cmdlets, connection models, and administration.
- Connect to Exchange Online PowerShell
Microsoft guidance for modern Exchange Online PowerShell connections and authentication.
- Get-EXOMailbox
Microsoft reference for optimized Exchange Online mailbox retrieval and property selection.
- Get-ConnectionInformation
Microsoft reference for inspecting Exchange Online PowerShell connection information.
- PowerShell remoting
Microsoft documentation for PowerShell remoting and PSSession concepts.