(Ab)using the REST API endpoints behind the new ExO cmdlets

Anyone that’s running admin tasks in Exchange Online is probably aware that a new, “V2” module has been released, featuring some faster and much more reliable cmdlets (in case you aren’t, watch this Ignite session). Those are backed by REST API endpoints, similar to the Graph API that covers most other workloads in Office 365. While Exchange Online is yet to support the Graph (or should it be the other way around?), the new REST endpoints can be exploited in a similar manner, as long as you know what you’re doing. Let’s see how.

First things first, you need to authenticate and in order to do that you need a token. To obtain a token, you must specify few bits of information, such as the clientID and resource, all of which you can obtain by running a Fiddler trace or in some cases by crawling the sign-in logs. Once you have all the details, you can either use the ADAL/MSAL binaries or directly issue a web request to obtain the token. I find it easier to use the former approach, so here’s a code snippet that loads the corresponding binaries and calls the AcquireTokenAsync method:

Add-Type -Path 'C:\Program Files\WindowsPowerShell\Modules\AzureAD\2.0.1.10\Microsoft.IdentityModel.Clients.ActiveDirectory.dll' 

$authContext3 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList "https://login.windows.net/michev.onmicrosoft.com"

$plat = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters -ArgumentList "Auto"

$authenticationResult = $authContext3.AcquireTokenAsync("https://outlook.office365.com", "fb78d390-0c51-40cd-8e17-fdbfab77341b", "urn:ietf:wg:oauth:2.0:oob",$plat);

Once you get the token, you can issue standard web requests against the corresponding endpoints. And in case you are wondering which endpoint you need and the exact syntax to use, you can easily obtain this information by running the corresponding new cmdlet with the -Debug switch. Here’s an example:

And here’s an example on how to get a list of all the mailboxes via the REST endpoint:

$authHeader = @{'Authorization'=$authenticationResult.Result.CreateAuthorizationHeader()}

$mailboxes = Invoke-RestMethod -Method Get -Uri "https://outlook.office.com/adminApi/beta/xxxxxxxx-352a-4eda-bece-09d0684d0cfb/Mailbox" -Headers $AuthHeader

$mailboxes.value

As another example, we can get the properties for a single mailbox:

Invoke-RestMethod -Method Get -Uri "https://outlook.office.com/adminApi/beta/xxxxxxxx-352a-4eda-bece-09d0684d0cfb/Mailbox('vasil@xxxxxx.info')" -Headers $AuthHeader 

@odata.context : https://outlook.office.com/adminApi/beta/xxxxxxxx-352a-4eda-bece-09d0684d0cfb/$metadata#Mailbox/$entity
@odata.id : https://outlook.office.com/adminApi/beta/xxxxxxxx-352a-4eda-bece-09d0684d0cfb/Mailbox(xxxxxxxx-888c-4b85-8871-c9766cb4791b')
@odata.editLink : https://outlook.office.com/adminApi/beta/xxxxxxxx-352a-4eda-bece-09d0684d0cfb/Mailbox(xxxxxxxx-888c-4b85-8871-c9766cb4791b'
ExternalDirectoryObjectId : xxxxxxxx-888c-4b85-8871-c9766cb4791b
UserPrincipalName : vasil@xxxxxx.info
Alias : vasil
DisplayName : Vasil Michev
EmailAddresses : {smtp:vasil@sb1.xxxxxx.info, SPO:SPO_xxxxxxxx-e473-451a-85e9-097d3c08307e@SPO_xxxxxxxx-352a-4eda-bece-09d0684d0cfb, SIP:vasil@xxxxxx.info, smtp:vasil@xxxxxx.onmicrosoft.com...}
PrimarySmtpAddress : vasil@xxxxxx.info
RecipientType : UserMailbox
RecipientTypeDetails : UserMailbox
Identity : vasil
Id : vasil
ExchangeVersion : 0.20 (15.0.0.0)
Name : vasil
DistinguishedName : CN=vasil,OU=michev.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=EURPR03A001,DC=prod,DC=outlook,DC=com
OrganizationId : EURPR03A001.prod.outlook.com/Microsoft Exchange Hosted Organizations/michev.onmicrosoft.com - EURPR03A001.prod.outlook.com/ConfigurationUnits/michev.onmicrosoft.com/Configuration
Guid : xxxxxxxx-4e45-4c28-a80f-b57b7441490f

And so on. Now, it goes without saying that this method is not supported in any way, so you should not use it in production. But, the samples above illustrate the inner workings of the new cmdlets, and they also hint what you can expect from the future Graph API endpoints for Exchange Online. To get more details on this as well as other tips on using the new cmdlets, join me and fellow MVP Ingo Gegenwarth on February 5th for the Working with the new Exchange Cmdlets webinar.

39 thoughts on “(Ab)using the REST API endpoints behind the new ExO cmdlets

  1. Chris Hill says:

    Worth noting that you can retrieve specific fields for the OData response with the following syntax:

    https://outlook.office.com/adminApi/beta//?$select=columnname1, columnname2

    And all available fields using:

    https://outlook.office.com/adminApi/beta//?PropertySet=All

    (The ?$select= syntax appears to map to the Get-EXO* -Properties cmdlet parameter, and the ?PropertySet= syntax appears to map to and accept the same values as the -PropertySets parameter)

    This dramatically increases the amount of data that can be accessed. I have written a guide on this here: https://minkus.medium.com/easily-connecting-between-power-query-power-bi-and-microsoft-graph-72333eb95a35

    Reply
  2. Volodymyr says:

    Hi, thanks for the post.
    Is there any information when such API is available in Graph API? When it can be used in production?

    Reply
  3. Saba says:

    Hi Vasil,
    I have a powershell scheduled script which fetches list of all shared mailboxes and distribution lists in SharePoint list. The script uses EXO V2 module which uses ADAL authentication. But I believe ADAL is getting depricated in June 2022, and Microsoft is asking to migrate to MSAL.
    How can I use MSAL token in Powershell to connect Exchange?
    Alternatively I tried to explore Graph API, but there is no api which provides list of all shared mailboxes and distribution lists from exchange.

    Reply
    1. Vasil Michev says:

      Just update your code to fetch the token via the corresponding MSAL routine, not much has changed there. Plus, ADAL will not stop working in 2022, they’re simply not investing in it anymore.

      Reply
      1. Patrick Schmidt says:

        Hey do you have any example on this?
        Currently i am using something like this:

        var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext($”https://login.microsoftonline.com/{_tenantId}”); // you can also use the v2.0 endpoint URL
        return (await authContext.AcquireTokenAsync(url, credential)).AccessToken;

        Url is “https://outlook.office.com” , for accessing GetMailBoxStatistics.
        Weirdly, those RestAPI calls deliver false itemsizedata
        https://outlook.office.com/adminApi/beta/xxx “/Mailbox(‘xxx’)/Exchange.GetMailboxStatistics()

        If you spy on the new exchange admin center, the URLs used are looking like this.
        https://admin.exchange.microsoft.com/beta/Mailbox(xxxxxxxx)/ExchangeAdminCenter.GetMailboxStatistics(Archive=false)

        Reply
        1. Vasil Michev says:

          Sorry Patrick, example of what? Getting token via MSAL or? You should not be using these endpoints for any production workloads though, the article is intended for “educational” purposes πŸ™‚

        2. Patrick Schmidt says:

          i wanted to add one subnote.
          I managed to get it working with MSAL, BUT only for graph API calls, azure management Calls and Office365 Communication calls.
          https://manage.office.com/api/v1.0/xxx/ServiceComms/CurrentStatus

          When i want to call
          https://outlook.office.com/adminApi/beta/xxxxxxxx-352a-4eda-bece-09d0684d0cfb/Mailbox('vasil@xxxxxx.info‘)

          I get an unauthorized with the token for
          scopes = new string[] { “https://manage.office.com/.default” };
          var result3 = await app.AcquireTokenForClient(scopes)
          .ExecuteAsync();

          So thats really weird :/

        3. Patrick Schmidt says:

          Sorry you are right.
          BUT i still have one issue, and it is official , because Exchange Online is the official power shell module.

          When using Get-EXOMailboxStatistics -Identity xxxx i get a false totalitemsize return. It shows only a few MB , but the mailbox is many GB in size.

          {“@odata.context”:”https://outlook.office365.com/adminapi/beta/xxxxxxxx/$metadata#Exchange.MailboxStatistics”,”DisplayName”:”xxxxxx”,”MailboxGuid”:”xxxxxxxx”,”DeletedItemCount”:7,”ItemCount”:437,”TotalDeletedItemSize”:73556,”TotalItemSize”:7194852}

          Is this a known bug?

        4. Vasil Michev says:

          Not able to reproduce this, I get the exact same values:

          Get-MailboxStatistics vasil | fl *ItemCount,Total*Size

          DeletedItemCount : 57485
          ItemCount : 68508
          TotalDeletedItemSize : 1.524 GB (1,636,153,674 bytes)
          TotalItemSize : 9.932 GB (10,664,905,771 bytes)

          and via REST

          @odata.context : https://outlook.office.com/adminApi/beta/xxxxxx-352a-4eda-bece-xxxxxxx/$metadata#Exchange.MailboxStatistics
          DisplayName : Vasil Michev
          MailboxGuid : xxxx-xxxx-xxxx-xxxx-xxxx
          DeletedItemCount : 57486
          ItemCount : 68508
          TotalDeletedItemSize : 1636158604
          TotalItemSize : 10664905771

  4. Paul Gideon says:

    Hi Vasil,

    Is it possible to create a Mailbox (using …/Mailbox and passing an object), or Add Permission (using …/MailboxPermission and passing an object) or sort of ?

    Thanks
    Paul

    Reply
    1. Vasil Michev says:

      The only supported way of doing this is via the cmdlets.

      Reply
  5. Carsten M says:

    Hi Vasil,

    do yo know anything about about a REST API endpoint behind the “New-MailUser” cmdlet? I guess https://outlook.office.com/adminApi/beta/tenant cannot be used fo that, right? And a second question: can I perform other CRUD operations than READ on that endpoint? So far I only was successful with reading accesses.

    Reply
    1. Vasil Michev says:

      Only a handful of operations are currently supported, most of them are Get-/READ ones. No support for New-MailUser and similar afaik.

      Reply
      1. Carsten M says:

        I thought so. Thank you you very much for your quick reply.

        Reply
  6. Dmitry says:

    Hey Vasil,
    I noticed that if you run https://outlook.office365.com/adminapi/beta/xxx/ it returns all commands available in the API, including the /CmdletInfo, then looks like we can expand further, i.e /CmdletInfo(‘Get-AcceptedDomain’).
    But the latter doesn’t work, returns 404 – any idea? πŸ™‚

    Thanks

    Reply
    1. Vasil Michev says:

      No idea, it’s all undocumented/unsupported/use at your own risk currently πŸ™‚

      Reply
  7. Tas Gray says:

    Hey Vasil,

    Great post, I’ve been hanging out for something like this.

    Considering that it’s now possible to connect to exchange online using the V2 module with a certificate in powershell, is it also possible to connect to the API using certificate? This would be phenomenal if possible.

    Reply
  8. Suny Abraham says:

    How do I access a Shared mailbox messages using API

    Reply
    1. Vasil Michev says:

      EWS or the Graph API both work just fine for this scenario.

      Reply
  9. Anishka says:

    Is it possible to get the Public folder mailbox list using rest api’s?

    Reply
    1. Vasil Michev says:

      No, PF mailboxes aren’t currently returned via the REST cmdlets/API.

      Reply
  10. Slava G says:

    Thanks Vasil,
    Actually I managed to dig Sharepoint via powershell, but Exchange is somehow (old commands) are different story.

    Reply
  11. Slava G says:

    Hi,
    Thanks for your explanation, is there any I can call those endpoint in my Java application ? as I need to get list of all shared mailboxes and the only way I found is using those new cmdlets, but I did a bit fiddler and see the request / response, and login session of course and can’t get access token valid for AdminAPI so calling to : https://outlook.office365.com/adminapi/beta/xxxx/Mailbox?RecipientTypeDetails=SharedMailbox reponses in 401.
    Please advise, if that API can’t be used what can be alternative ?
    Thanks !!!!

    Reply
    1. Vasil Michev says:

      The article above gives you an example on how to get a token via the ADAL library, should be straightforward to do it in Java as well. Apart from that you need actual permissions in Exchange Online.

      Reply
      1. Slava G says:

        Well, I’m getting the token, but it’s only for those scopes: “Calendars.ReadWrite Calendars.ReadWrite.All Contacts.ReadWrite Contacts.ReadWrite.All EWS.AccessAsUser.All Files.ReadWrite Files.ReadWrite.All full_access_as_user Group.Read.All Group.ReadWrite.All Mail.ReadWrite Mail.ReadWrite.All offline_access Tasks.Read Tasks.ReadWrite User.Read.All User.ReadBasic.All” and I can’t get it like in powershell : “scope=AdminApi.AccessAsUser.All FfoPowerShell.AccessAsUser.All RemotePowerShell.AccessAsUser.All”

        Reply
        1. Vasil Michev says:

          Yeah, you need to get it for the EXACT application that is listed above, not for any custom apps you’ve registered. MS is working on providing a support for the latter, stay tuned.

        2. Slava G says:

          So I tried to fiddler Get-Mailbox (prev generation command) to see if it’s uses different API , but somehow it’s not captured by fiddler.

  12. oded mass says:

    Hi, Is it possible to use the api with client_credentials grant_type.
    If so how can we set the api permissions for the application?
    Thanks.

    Reply
    1. Vasil Michev says:

      Nope, PowerShell always runs in the context of a user.

      Reply
      1. oded mass says:

        Hi, Trying the api I am getting an error The user … isn’t assigned any management roles
        Which roles are required and how do I set them.
        Thanks.

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.