Route Exchange Online PowerShell through GraphWarden

Connect-ExchangeOnline can route through GraphWarden by supplying a proxy-acquired access token and pointing the module at the proxy as its Graph endpoint. Every existing Exchange Online cmdlet (Get-Mailbox, Set-CASMailbox, Get-CASMailbox, and the rest) then flows through the proxy and is subject to your ruleset's endpoint allowlist. This page shows the connect pattern, the ruleset considerations for Exchange workloads, and the legacy-REST cmdlets that bypass the proxy entirely.

ExchangeOnlineManagement v3.x

Prerequisites

- PowerShell 7+ (recommended) or Windows PowerShell 5.1 - `ExchangeOnlineManagement` module v3.x — install with `Install-Module ExchangeOnlineManagement -Scope CurrentUser` - A GraphWarden proxy URL reachable from the script host - A Proxy Client ID and Proxy Client Secret issued by GraphWarden App Admin for the App Identity that represents this script - For certificate-based Exchange authentication: a certificate thumbprint registered on the Azure AD app registration GraphWarden brokers for

Route an Exchange Online admin script with an AI assistant

Route my Exchange Online admin script through GraphWarden.

My script currently calls Connect-ExchangeOnline with certificate-based app-only auth against Exchange Online directly.

I need:
1. The pattern to acquire a proxy JWT from GraphWarden's /proxy/token endpoint
2. A Connect-ExchangeOnline -AccessToken invocation that routes through the proxy
3. Guidance on which of my cmdlets transit Microsoft Graph (routed through GraphWarden) vs the legacy Exchange REST endpoint (not routed)

Reference documentation: https://graphwarden.com/llms.txt

Ask me for my proxy URL, proxy credentials, and the cmdlet list the script uses today.

Reference: llms.txt

The override pattern

Modern ExchangeOnlineManagement v3.x uses Microsoft Graph under the hood for the cmdlets that have been migrated to Graph (roughly: mailbox read operations, user-level calendar queries, some transport rule reads). Those cmdlets honour the MICROSOFT_GRAPH_API_ENDPOINT environment variable the same way Microsoft.Graph cmdlets do, and they accept -AccessToken on Connect-ExchangeOnline so you can pass a GraphWarden-issued proxy JWT.

  • Token acquisition — post your Proxy Client ID and Proxy Client Secret to POST {proxy-host}/proxy/token. The response is a proxy JWT; pass it to Connect-ExchangeOnline as -AccessToken.
  • Graph endpoint override — set MICROSOFT_GRAPH_API_ENDPOINT to the proxy URL BEFORE Connect-ExchangeOnline. Graph-backed cmdlets read this at connection time.
# 1. Configuration — read from the environment.
$ProxyUrl     = $env:GRAPHWARDEN_PROXY_URL     # e.g. https://proxy.example.com
$ClientId     = $env:GRAPHWARDEN_CLIENT_ID     # Proxy Client ID from App Admin
$ClientSecret = $env:GRAPHWARDEN_CLIENT_SECRET # Proxy Client Secret from App Admin
$Organization = 'contoso.onmicrosoft.com'      # Your Exchange Online tenant domain

if (-not $ProxyUrl -or -not $ClientId -or -not $ClientSecret) {
    throw "Set GRAPHWARDEN_PROXY_URL, GRAPHWARDEN_CLIENT_ID, and GRAPHWARDEN_CLIENT_SECRET before running."
}

# 2. Acquire a proxy JWT via client_credentials against the proxy token endpoint.
$TokenResponse = Invoke-RestMethod `
    -Method Post `
    -Uri "$ProxyUrl/proxy/token" `
    -ContentType 'application/x-www-form-urlencoded' `
    -Body @{
        grant_type    = 'client_credentials'
        client_id     = $ClientId
        client_secret = $ClientSecret
    }

# 3. Point Graph-backed Exchange cmdlets at the proxy BEFORE Connect-ExchangeOnline.
$env:MICROSOFT_GRAPH_API_ENDPOINT = $ProxyUrl

# 4. Connect using the proxy JWT. -ShowBanner:$false quiets the module banner.
Connect-ExchangeOnline `
    -AccessToken  $TokenResponse.access_token `
    -Organization $Organization `
    -ShowBanner:$false

# 5. Verification call — Get-Mailbox routes through the proxy on v3.x.
Get-Mailbox -ResultSize 1 | Select-Object DisplayName, PrimarySmtpAddress

The -Organization parameter is mandatory for app-only / token-based connections so the module knows which tenant to scope cmdlets to. Omit it only if you are connecting with user-delegated auth, which is not the GraphWarden path.

Configuring ruleset for Exchange workloads

Exchange Online cmdlets call a mix of Microsoft Graph endpoints and Exchange-specific REST endpoints. Your GraphWarden ruleset must cover the Graph-backed paths; the Exchange-specific paths bypass the proxy and are NOT subject to ruleset enforcement (see the next section).

Graph-backed endpoints to include in your allowlist for common Exchange workloads:

Workload Graph endpoint pattern
Mailbox properties (read) /v1.0/users/{id}/mailboxSettings, /v1.0/users/{id}?$select=...
Mailbox messages /v1.0/users/{id}/messages and /v1.0/users/{id}/mailFolders/*/messages
Calendar reads /v1.0/users/{id}/calendar, /v1.0/users/{id}/events
Distribution groups (read) /v1.0/groups, /v1.0/groups/{id}/members

For workloads that are NOT Graph-backed (transport rules, message-trace queries, some mail-flow cmdlets), plan to surface those in your compliance audit through Exchange Online's own audit log rather than through GraphWarden — the proxy never sees those requests.

Some Exchange Online cmdlets route through the legacy Exchange REST endpoint (`outlook.office365.com`) rather than Microsoft Graph. Examples include most transport-rule cmdlets, `Get-MessageTrace`, and the older shell-proxy cmdlets. These calls do NOT transit GraphWarden and are not covered by your ruleset. Identify the endpoint a cmdlet uses by running it with `-Verbose` and inspecting the request URIs in the output.

Common admin scenarios

Four short scenarios showing the common operations. Each assumes the connection from the override pattern above is already established.

List mailboxes with size and quota

Get-Mailbox -ResultSize 100 `
    | Get-MailboxStatistics `
    | Select-Object DisplayName, TotalItemSize, ItemCount, LastLogonTime `
    | Sort-Object TotalItemSize -Descending

Get-MailboxStatistics reads mailbox size counters from Graph on v3.x; the Graph endpoint is subject to ruleset enforcement.

Update mail routing for a single user

Set-Mailbox `
    -Identity 'alice@contoso.com' `
    -ForwardingSmtpAddress 'compliance-archive@contoso.com' `
    -DeliverToMailboxAndForward:$true

Mailbox property updates go through Microsoft Graph on v3.x; a ruleset with action: block on PATCH /v1.0/users/{id} will stop this cmdlet at the proxy.

Grant full-access mailbox permission

Add-MailboxPermission `
    -Identity 'shared-inbox@contoso.com' `
    -User 'helpdesk@contoso.com' `
    -AccessRights FullAccess `
    -AutoMapping:$false

Mailbox permission changes are audit-heavy; even with action: allow, every call surfaces as a GraphWarden audit event with the matched rule id and the payload summary.

Export mailbox permissions to CSV

Get-Mailbox -ResultSize Unlimited `
    | ForEach-Object {
        Get-MailboxPermission -Identity $_.Identity `
            | Where-Object { $_.User -notlike 'NT AUTHORITY\*' -and -not $_.IsInherited }
    } `
    | Select-Object Identity, User, AccessRights `
    | Export-Csv -Path '.\mailbox-permissions.csv' -NoTypeInformation

Useful for compliance reviews. Run against a non-production tenant first — ResultSize Unlimited over a large tenant can trigger proxy rate-limit rules depending on your ruleset.

Troubleshooting

The five failure modes below cover the common surprises when running Exchange Online cmdlets against GraphWarden.

Connect-ExchangeOnline returns a modern-auth-only error

The ExchangeOnlineManagement v3.x module has dropped support for Basic authentication; all connections must use OAuth 2.0. If you see the error Basic auth disabled or modern authentication required, the module is working as designed — you must supply -AccessToken (the GraphWarden path) or -CertificateThumbprint (direct to Exchange). There is no fallback to username/password.

Cmdlet returns 401 Unauthorized after an hour of scripted runs

Proxy JWTs expire after roughly one hour. The Exchange Online module does not automatically refresh tokens supplied via -AccessToken. For long-running scripts, wrap the connect + cmdlet call in a helper that catches 401, calls Disconnect-ExchangeOnline, reacquires a fresh proxy JWT from /proxy/token, and reconnects.

Cmdlet returns 403 Forbidden with a GraphWarden rule id in the body

A rule in the bound ruleset returned a block verdict on the Graph endpoint the cmdlet called. The response body names the matched rule. Confirm that the Graph endpoint pattern is covered by your ruleset's allowlist, and use the What-If Simulator to preview rule evaluation without hitting Graph.

Cmdlet succeeds but no audit entry appears in GraphWarden

The cmdlet used the legacy Exchange REST endpoint (outlook.office365.com or equivalent), not Microsoft Graph. GraphWarden does not intercept Exchange REST, so the request bypassed the proxy. This is expected for transport-rule and some mail-flow cmdlets; audit those via Exchange Online's native audit log.

502 Bad Gateway on Get-Mailbox

The proxy is reachable but Microsoft Graph returned a non-2xx upstream response, or the Exchange-on-Graph session the module established on connect has expired. Check the proxy's health endpoint (GET /health), then call Disconnect-ExchangeOnline and reconnect. The module re-establishes its Graph session on Connect-ExchangeOnline.

Last reviewed:

Last reviewed: .