Hero Banner

Secure Application Model

Learn and ask questions on how to implement secure application model

Level 5 Contributor

Retirement of the Legacy Exchange Online Public Client ID (app ID a0c73c16-a7e3-4564-9a95-2bdf47383716)

I figured I'd make a new topic for this. Per Fridays announcement, the Exchange app we use to get accesstokens will cease to function/exist by March 31st.

January 2023 announcements - Partner Center | Microsoft Learn


As mentioned in the other topic the Exchange team added a -Accesstoken parameter in the final weeks of 2022 which made the following possible:


# Define Exchange Access Token splat
$exchangeAccessTokenSplat = @{
    ApplicationId = 'a0c73c16-a7e3-4564-9a95-2bdf47383716' # Exchange Online app
    RefreshToken = $ExchangeRefreshToken
    Scopes = 'https://outlook.office365.com/.default'
    Tenant = $TenantID # Customer tenant ID

# Get Exchange Online access token
$exoToken = New-PartnerAccessToken @exchangeAccessTokenSplat

# Connect to Exchange Online
Connect-ExchangeOnline -DelegatedOrganization $TenantID -AccessToken $exoToken.AccessToken



Given the availability of the -AccessToken parameter on the preview version I would expect there to be some new authentication flow to request the refresh token and access token.


@JanoschUlmer is there anything else we can do to put pressure on the topic?


cc: @ClaudioStallone @sansbacher @Leon-anspired @KoenHalfwerk 

Level 5 Contributor

So... in hindsight this feels very obvious. But with some hints (thanks @KelvinTegelaar!) I've put together the following,


To add to the list of grants in New-PartnerCustomerApplicationConsent:


$ExchangeGrant = New-Object -TypeName Microsoft.Store.PartnerCenter.Models.ApplicationConsents.ApplicationGrant
$ExchangeGrant.EnterpriseApplicationId = "00000002-0000-0ff1-ce00-000000000000"
$ExchangeGrant.Scope = "Exchange.Manage"



Also add the same "Exchange.Manage" grant to the app in your own CSP tenant. (Use "Add a Permission" > "APIs my organization uses" > search for "Office 365 Exchange Online" or "00000002-0000-0ff1-ce00-000000000000" and add "Exchange.Manage").


After that you can use the following method to authenticate to EXO:


# Define ExchangeTokenSplat parameters
$ExchangeTokenSplat = @{
    ApplicationId = $CSPappId # AppID in CSP tenant
    Scopes = 'https://outlook.office365.com/.default'
    ServicePrincipal = $true
    Credential = (New-Object System.Management.Automation.PSCredential ($CSPappId, (ConvertTo-SecureString $CSPappSecret -AsPlainText -Force)))
    RefreshToken = $PartnerCenterRefreshToken
    Tenant = $TenantID # Customer TenantID

# Get $ExchangeToken
$ExchangeToken = New-PartnerAccessToken @ExchangeTokenSplat

# Connect to MgGraph
Connect-ExchangeOnline -DelegatedOrganization $TenantID -AccessToken $ExchangeToken.AccessToken



From some quick testing I can both request data and make changes so it feels like everything works.

So I guess my conclusion is that the folks writing the learn articles for ExchangeOnlineManagement just haven't considered the Partner angle at all?


Edit: So far I haven't seen a way to update existing PartnerCustomerApplicationConsents so I guess removing the svc principal in customer tenants and running New-PartnerCustomerApplicationConsent again is the only option?
Edit2: I went the way of connecting to the customer tenants and using 
New-MgOauth2PermissionGrant to add the permission grant.

View solution in original post

Level 4 Contributor

So after zero help from Microsoft. I managed to solve the issue myself. In the documentation: https://learn.microsoft.com/en-us/powershell/partnercenter/exchange-online-gdap-app?view=partnercenterps-3.0

It says to use this:
$token = New-PartnerAccessToken -ApplicationId $AppId -Scopes 'https://outlook.office365.com/.default' -ServicePrincipal -Credential $appcredential -Tenant $CustomerTenantId -RefreshToken $PartnerAccesstoken.refreshToken
Connect-ExchangeOnline -DelegatedOrganization $CustomerTenantId -AccessToken $token.AccessToken

For $CustomerTenantId I was using the customers tenant id, e.g. the guid. To fix my issue, I instead used the customers domain. e.g.

$token = New-PartnerAccessToken -ApplicationId $AppId -Scopes 'https://outlook.office365.com/.default' -ServicePrincipal -Credential $appcredential -Tenant "contoso.com" -RefreshToken $PartnerAccesstoken.refreshToken
Connect-ExchangeOnline -DelegatedOrganization "contoso.com" -AccessToken $token.AccessToken

Might be worth Microsoft updating their documentation to reflect this.

View solution in original post

Community Manager

@aconn21 I am so sorry you have been having troubles here. What is your support ticket number? I would like to try and escalate it for you. 

Visitor 2

Thank you for sharing, this works for me!

Community Manager

@aconn21 can you submit your feedback here? You never know, maybe it will help them take a look and make some changes! 😁

Level 4 Contributor

Sure, once you've given me the correct link 😂

Community Manager

OH!!! 😂 My mistake!


I have corrected the link, thanks for letting me know! 


@aconn21 @rvdwegen 

Level 5 Contributor

I think that's the wrong link 😂

Level 5 Contributor

I can't imagine that's intended behavior.


By chance did you test different variations? (just the DelegatedOrganization as the domain or just the -tenant as the domain)?

Level 4 Contributor

I've tested this now. With New-PartnerAccessToken you can use the tenant id or the tenant domain. With Connect-ExchangeOnline you have to use the tenant domain.

Level 5 Contributor

For what its worth, I've since ran into the same issue when directly hitting the Exchange Online API. Requesting the token for it as the domain instead of the id doesn't make a difference for that.

Visitor 1

I was testing this myself, and it seems that it doesn't matter whether you use the Tenant ID or domain name in either of the token request or the Exchange Online request. The call succeeds randomly regardless. I tested the endpoint by repeatedly requesting, and had 22 successes out of 200 while trying to run `Get-InboxRule {mailboxname}`. That's a 90% failure rate. Really stand-up job from Microsoft as expected. I would have used a higher sample size, but the requests are so ungodly slow that I didn't want to wait any longer.

Level 6 Contributor

Hi @calebstewart , I had the same problem: inconsistent access when connecting using the ExO V3 module, especially with any commands that tried to write/change/delete/create/modify anything! I would get errors like:


Write-ErrorMessage : Ex34AFDD|Microsoft.Exchange.Data.Directory.ADOperationException|An RPC error occurred while the member count of an address list was being read. The error is "Error 0x5 (Access is denied) from cli_NspiBind


Write-ErrorMessage : |Microsoft.Exchange.Data.Directory.InsufficientPermissionsException|Source
server:BL3P221MB0612.NAMP221.PROD.OUTLOOK.COM doesn't have write permission to target
DC:YQBPR01A004DC09.CANPR01A004.PROD.OUTLOOK.COM. Usually it indicates that target forest isn't an account partition of source forest. Additional information: Insufficient access rights to perform the operation.
Active directory response: 00002098: SecErr: DSID-031514A0, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0


Ex574388|Microsoft.Exchange.Configuration.Tasks.TaskException|The parent object for 'Parent Company Org Sharing Name' could not be found. Please check that childTenant.onmicrosoft.com\Federation exists.


I followed @aconn21 's suggestion to use the Customer's Primary Domain Name and that helps, but not entirely. Then I found a post indicating you HAD to use the Customer's .onmicrosoft.com domain - no matter what! And that fixed my issues.


Unfortunately it's NOT true that abccompany.com always has abccompany.onmicrosoft.com, and sometimes the Domain Name returned from either Get-PartnerCustomer or Get-AzureADContract is not the onmicrosoft.com domain. So here's my solution:



# Assumptions: $Customer is from Get-AzureADContract, $AzureADAppId is the ClientID of your Secure App in your CSP/Partner tenant, and $pcCreds is that ClientID + AppSecret, and of course $RefreshToken is your normal Refresh Token for the Secure App (not the one tied to the old ExO App ID, a0c7....3716)

# Also assumes you have added the 'Exchange.Manage' API Permission for 'Office 365 Exchange Online' to BOTH your Secure App AND to the list of Grants you create with New-PartnerCustomerApplicationConsent for each Customer

# Get an Access Token for the Customer. Using the Customer's TenantID (ContextId) is fine here.
$exTok = New-PartnerAccessToken -Tenant $Customer.CustomerContextId -RefreshToken $RefreshToken -ServicePrincipal -Scopes "https://outlook.office365.com/.default" -ApplicationId $AzureADAppId -Credential $pcCreds

# Get the Customer's .onmicrosoft.com domain (ignoring any .mail.onmicrosoft.com domains)
# Assumes you have already done the Connect-AzureAD bit for the Customer
# NOTE: this should really be an MS Graph call...
$onDomain = Get-AzureAdDomain | where Name -match '^(?!.*\.mail\.onmicrosoft\.com$).*onmicrosoft\.com$'

# Connect to ExO using V3 module, which must use the .onmicrsoft.com domain
Connect-ExchangeOnline -AccessToken $exTok.AccessToken -DelegatedOrganization $onDomain.Name -ShowBanner:$False -ShowProgress:$false



That seems to work in all my testing! And yes, migrating all my AzureAD code to MSGraph is next on my list! This does NOT appear to be mentioned in the official MS documentation, they just mention Customer Tenant ID (which I take to be either the GUID or the Primary Domain Name - the name that appears in the CSP Partner Center).


Big thanks to @rvdwegen for first posting this. I meant to comment earlier but I've been super busy with other stuff the last few months. Finally getting to this just before the deadline! Has @JanoschUlmer moved on? I wanted to also thank him for all his work! And to everyone else who has helped get this working!





Level 5 Contributor


(Invoke-RestMethod -Method 'GET' -Headers $header -Uri "https://graph.microsoft.com/v1.0/domains?").value | Where-Object { $_.isInitial -eq $true }


Since the AzureAD module is also on its way out haha.

Unfortunately all the commands and endpoints that I'm aware of that you can hit from your CSP tenant context including Get-PartnerCustomer and Get-AzureADContract list the *default domain*, not the onmicrosoft domain. So the only way to always get the onmicrosoft is to go looking in the customer tenant itself. (this is where that benefit of on the fly context switching I mentioned comes into play).

Level 6 Contributor

@rvdwegen I just found this same endpoint! 🙂


And I also found Invoke-GraphRequest - which is part of just the base "Microsoft.Graph.Authentication" module. With it I can avoid having to install all those auto-generated MgGraph commands (but still choose to use a few such as Microsoft.Graph.Users or Microsoft.Graph.Groups if I wanted). And it eliminates some of the boiler plate, so you can narrow that down to:


(Invoke-GraphRequest -Method GET -Uri '/v1.0/domains').value | where IsInitial


Since it knows the URL, and doesn't need to re-auth if you use Connect-MgGraph. For my usage I don't (currently) need to switch back and forth to my CSP tenant and the Customer's tenant, so I think this is a good compromise. The data comes back as a Hashtable, but it can be cast to a [PSCustomObject] if needed.


And yeah, I couldn't see any way to get this info from my tenant. But most of my scripts will involve connecting to both (AAD or now Graph) and Exch Online so it's not a big deal.


Now I just have to go through each script looking for any -AzureAD or -Msol commands and update them!.... (which, I know, I should have done ages ago...)


Level 5 Contributor

fyi, last I tried there's some incompatibility between Microsoft.Graph.Authentication and 7.1/7.2 Automation runbooks.

Community Manager

@sansbacher  yest @JanoschUlmer has moved on but you can find him on LinkedIn and I will forward this post on to him as well. So glad you got it up and working!! YAY!! 🔥


@rvdwegen : Thanks for providing this info - I have a call later today with some folks responsible for thism where I hope I can get some clarity on supported options, or options planned to be supported. The example of a working approch might help a lot here.


Note that as of today,the only approach officially documented is using the app-only authentication as called out in Exchange documentation: App-only authentication in Exchange Online PowerShell and Security & Compliance PowerShell | Microsoft Learn - but I hope your approach will also be considered supported.

Kind regards, Janosch (Note: Leaving role as of March 2023, don't expect further answers. Connect with me via LinkedIn: https://linkedin.com/in/janoschulmer)