Exchange Online Mailbox Access from PowerShell via REST.

Update: 11/2/2016 Sadly, what is described in this article does not work as-is now that the Exchange REST API has transitioned to production. There are two reasons for this:

  • In production, the API endpoints have different URLs.
  • Basic authentication is no longer supported with the REST API. Instead, OAuth2 must be used (which is something of a pain to get working in PowerShell).

Hopefully, I’ll soon have a new article addressing these changes.

When I recently attended the 2014 Microsoft Exchange Conference here in Austin, I found myself frequently having to pick and choose between simultaneous sessions that were of interest to me.  Since we are early in the planning stages of upgrading our on-premise Exchange 2010 infrastructure to 2013, I tended to steer towards sessions that would help me on the project. Until I saw a recent blog post by Glen Scales, I had little clue about just what I was missing.

It turns out that Microsoft has rolled out a preview release of a new REST-based API for Exchange Online. It is largely incomplete at this point, but it is very simple to learn, and it is easily leveraged from within PowerShell. Glen’s blog article makes mention of PS version 4 being required, but version 3 is sufficient. This is to have access to the Invoke-RestMethod cmdlet that was added in version 3.

Now, having been an Exchange administrator for the last decade and a half, it has been a while since I have kept abreast of the latest and greatest offerings in the world of programming.  When I first saw Glen’s post, I had to do a little research to bring myself up to speed on what the heck REST is. It is essentially a light-weight alternative to SOAP/XML-based web services.  Whereas web services generally involve submitting (via the web) an XML bundle containing the commands to be executed and any data being submitted, with the results coming back as another XML blob, REST pretty much encodes the commands in the URI being accessed (with associated data being sent along in a JSON or XML blob), with the response coming back in XML or JSON format. The preview API being discussed here returns data primarily in JSON format, with a handful of exceptions.  (Happily, using REST through PowerShell causes the returned JSON data to be stuffed into a structured PowerShell object, so we have no need here to go over how to parse JSON. If we had to, fortunately, PowerShell does include the ConvertTo-Json and ConvertFrom-Json cmdlets, but I don’t explore those in this article.)

Here is a quick and dirty run-through of some of the basics, starting off with retrieving the list of available “entities” and “actions” (somewhat equivalent of retrieving the WSDL for SOAP/XML). While this can be done via PowerShell, it is simpler to navigate the results by pointing a web browser to https://outlook.office365.com/EWS/OData/$metadata and authenticating when prompted. (ADFS authentication works fine, here.)  The entities listed (presented in XML format) are the objects which can be read and manipulated, along with their attributes. Actions are the verbs that can be invoked.

Before we dive into some actual examples of invoking REST calls through PowerShell, I should note that there are two methods of encoding into the URI what mailbox is being manipulated. One is to default to the mailbox associated with the authentication credentials used by invoking the “/Me” path:

https://outlook.office365.com/ews/odata/Me

The other is to explicitly specify the mailbox via its SMTP address (assuming that the authenticated account has either Full Access privileges on the mailbox or appropriate sharing privileges on the folder being accessed) as follows:

https://outlook.office365.com/ews/odata/Users(‘username@domain.com’)

The entities and actions to be performed on those entities are then appended to the URI as shown in the examples which follow. We’ll start out by establishing our authentication, then retrieving a list of unread messages in the authenticated user’s Inbox:

PS C:\> $cred = get-credential

cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
PS C:\> Invoke-RestMethod -Uri "https://outlook.office365.com/ews/odata/Users('its.jimi.hendrix@austin.utexas.edu')/Inbox/Messages?`$filter=IsRead eq false" -Credential $cred | foreach-object{$_.value | select Sender,Subject,DateTimeReceived}

Sender                                  Subject                                 DateTimeReceived
------                                  -------                                 ----------------
@{Name=Test Mailbox; Address=testm... This is a test                          2014-04-29T20:47:35Z
@{Name=Test Mailbox; Address=testm... Have you read me?                       2014-04-29T19:40:31Z

Note that I am only displaying a handful of attributes for each message. Let us take a closer look at one of the messages to see the full gamut of information retrieved, also demonstrating the use of subject keyword filtering.  We’ll store it into a variable so that we can work more with it. I’ve chosen a test message whose body is in plain text format to keep the size small for display clarity:


PS C:\> $message=Invoke-RestMethod -Uri "https://outlook.office365.com/ews/odata/Users('its.jimi.hendrix@austin.utexas.e
du')/Inbox/Messages?`$filter=contains(Subject,'test')" -Credential $cred | foreach-object{$_.value }
PS C:\> $message

@odata.id                        : https://outlook.office365.com/EWS/OData/Users('its.jimi.hendrix@austin.utexas.edu')/
                                   Messages('AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTo
                                   cvyM47SvruBwBCjm8X6K-UR7VnjIWDMRNeAAAACWCRAADsGKiQ9_bQQ6Ey8tGr9WOeAADjdompAAA=')
@odata.editLink                  : https://outlook.office365.com/EWS/OData/Users('its.jimi.hendrix@austin.utexas.edu')/
                                   Messages('AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTo
                                   cvyM47SvruBwBCjm8X6K-UR7VnjIWDMRNeAAAACWCRAADsGKiQ9_bQQ6Ey8tGr9WOeAADjdompAAA=')
Id                               : AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTocvyM47Svru
                                   BwBCjm8X6K-UR7VnjIWDMRNeAAAACWCRAADsGKiQ9_bQQ6Ey8tGr9WOeAADjdompAAA=
ChangeKey                        : CQAAABYAAADsGKiQ9+bQQ6Ey8tGr9WOeAADjcfvF
ClassName                        : IPM.Note
Subject                          : This is a test
BodyPreview                      : This is only a test. Howdy.
Body                             : @{ContentType=Text; Content=This is only a test. Howdy.

                                   }
Importance                       : Normal
Categories                       : {}
HasAttachments                   : False
ParentFolderId                   : AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQAuAAAAAACM0mZeOZNpTocvyM47Svru
                                   AQBCjm8X6K-UR7VnjIWDMRNeAAAACWCRAAA=
From                             : @{Name=Test Mailbox; Address=testmbox@austin.utexas.edu}
Sender                           : @{Name=Test Mailbox; Address=testmbox@austin.utexas.edu}
ToRecipients                     : {@{Name=Jimi Hendrix (Test Mailbox); Address=its.jimi.hendrix@austin.utexas.edu}}
CcRecipients                     : {}
BccRecipients                    : {}
ReplyTo                          : {}
ConversationId                   : AAQkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQAQABqYlCcNlUc6te8jFvihy58=
DateTimeReceived                 : 2014-04-29T20:47:35Z
DateTimeSent                     : 2014-04-29T20:47:31Z
IsDeliveryReceiptRequested       : False
IsReadReceiptRequested           : False
IsDraft                          : False
IsRead                           : False
EventId                          :
MeetingMessageType               : None
DateTimeCreated                  : 2014-04-29T20:47:34Z
LastModifiedTime                 : 2014-04-29T20:47:35Z
Attachments@odata.navigationLink : https://outlook.office365.com/EWS/OData/Users('its.jimi.hendrix@austin.utexas.edu')/
                                   Messages('AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTo
                                   cvyM47SvruBwBCjm8X6K-UR7VnjIWDMRNeAAAACWCRAADsGKiQ9_bQQ6Ey8tGr9WOeAADjdompAAA=')/Att
                                   achments

Now let’s get to the good stuff. Suppose we wish to send a message.  The examples Glen provides don’t exactly make clear how to do this via PowerShell, but it isn’t that hard to work out from the API docs. The first step is to construct the JSON object containing the message data, then to use a POST command to create the message in Drafts. This is followed by using a SEND command (referencing the Id of the newly-created message) to actually send it. (If we wished, we could modify the draft prior to sending by updating the message with a PATCH method, but that is not illustrated here.)

PS C:\> $uri = "https://outlook.office365.com/ews/odata/Users('its.jimi.hendrix@austin.utexas.edu')/Drafts/Messages?'"
PS C:\> $contentType = "application/json;odata.metadata=full"
PS C:\> $body = "{
>> ""@odata.type"": ""#Microsoft.Exchange.Services.OData.Model.Message"",
>> ""Subject"": ""This is a send test"",
>> ""Importance"": ""High"",
>> ""Body"": {
>> ""ContentType"": ""HTML"",
>> ""Content"": ""I'm sending this via REST!""
>> },
>> ""ToRecipients"": [
>> {
>> ""Name"": ""Test Mailbox"",
>> ""Address"": ""testmbox@austin.utexas.edu""
>> }
>> ]
>> }"
>>
PS C:\> $new= Invoke-RestMethod -Uri $uri -Method POST -ContentType $contentType -Credential $cred -Body $body

PS C:\> $new
@odata.context : https://outlook.office365.com/EWS/OData/$metadata#Users('its.jimi.hendrix%40austin.u
texas.edu')/Drafts/Messages/$entity
@odata.id : https://outlook.office365.com/EWS/OData/Users('its.jimi.hendrix@austin.utexas.edu')/
Messages('AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTo
cvyM47SvruBwBCjm8X6K-UR7VnjIWDMRNeAAAACWCcAADsGKiQ9_bQQ6Ey8tGr9WOeAADjds-1AAA=')
@odata.editLink : https://outlook.office365.com/EWS/OData/Users('its.jimi.hendrix@austin.utexas.edu')/
Messages('AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTo
cvyM47SvruBwBCjm8X6K-UR7VnjIWDMRNeAAAACWCcAADsGKiQ9_bQQ6Ey8tGr9WOeAADjds-1AAA=')
Id : AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTocvyM47Svru
BwBCjm8X6K-UR7VnjIWDMRNeAAAACWCcAADsGKiQ9_bQQ6Ey8tGr9WOeAADjds-1AAA=
ChangeKey : CQAAABYAAADsGKiQ9+bQQ6Ey8tGr9WOeAADjcpGv
ClassName : IPM.Note
Subject : This is a send test
BodyPreview : I'm sending this via REST!
Body : @{ContentType=HTML; Content=<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
I'm sending this via REST!
</body>
</html>
}
Importance : High
Categories : {}
HasAttachments : False
ParentFolderId : AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQAuAAAAAACM0mZeOZNpTocvyM47Svru
AQBCjm8X6K-UR7VnjIWDMRNeAAAACWCcAAA=
From :
Sender :
ToRecipients : {@{Name=Test Mailbox; Address=testmbox@austin.utexas.edu}}
CcRecipients : {}
BccRecipients : {}
ReplyTo : {}
ConversationId : AAQkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQAQAH2kuI7ID95PpX2r53t2T6w=
DateTimeReceived : 2014-04-29T22:00:17Z
DateTimeSent : 2014-04-29T22:00:17Z
IsDeliveryReceiptRequested : False
IsReadReceiptRequested : False
IsDraft : True
IsRead : True
EventId :
MeetingMessageType : None
DateTimeCreated : 2014-04-29T22:00:17Z
LastModifiedTime : 2014-04-29T22:00:17Z
Attachments@odata.navigationLink : https://outlook.office365.com/EWS/OData/Users('its.jimi.hendrix@austin.utexas.edu')/
Messages('AAMkADQ0MWIxYThmLWE4YzItNDM4MS1hOTM0LTBhY2MzNGMzYmQwYQBGAAAAAACM0mZeOZNpTo
cvyM47SvruBwBCjm8X6K-UR7VnjIWDMRNeAAAACWCcAADsGKiQ9_bQQ6Ey8tGr9WOeAADjds-1AAA=')/Att
achments
PS C:\> Invoke-RestMethod -Uri "$($new.'@odata.id')/Send" -Method POST  -Credential $cred 

I should note here that when testing this final Send command by leveraging an account with Send As rights on the test mailbox, I encountered an odd “Mailbox move in progress” error, despite the fact that such a move is NOT in progress for any of the mailboxes with which I was dealing.  I suspect that this is a bug in this fledgling API.  When I used the actual credentials of the account from which I was attempting to send, all worked as expected.

There are the basics in a nutshell. Note that I’ve only dealt here with reading and sending messages.  I have not touched upon forwarding or replying, or upon dealing with other Exchange objects such as Contacts or Calendar items (including accepting or declining), but there should be enough here to provide a jumping-off point in experimenting with the API.

 For Further Information