This is Part3 in my continuing EWS Managed API and Powershell how to
series, in this post im going to look at using Mailbox Items in
Exchange Web Services. The single most important thing obviously within a mailbox is the content and one of Exchanges main strengths is the richness and flexibility of the content and item types that it can store and process. So in this post im going to cover the complexities of processing the different Items that are stored in a mailbox in different way to solve various everyday problems.
Items, Objects and Properties
Some of the Items you might find in an Exchange Mailbox include Email, Contacts, Calendar Appointments, Meeting, Tasks and other custom Item types. The data that constitutes an Exchange Item for example the Subject of an email message is stored as one or more properties in the Exchange Mail-Store associated with that Item. Looking at the Subject example a little more deeply the underlying Property for the subject is PidTagSubject http://msdn.microsoft.com/en-us/library/cc815720.aspx . This sounds easy but with Exchange Items there aren't just one level of properties for example an Email will generally have a collection of properties for the Item, then every recipient is a separate object with its own properties and then possible one more attachments with their own properties on each attachment.
This maybe a little too much detail but it is important to consider when you need to do more complex things with EWS for the most part the Managed API abstracts out these underlying properties into Strongly type objects for example EmailMessage, Contact or Appointment. For a quick example lets look at two ways of accessing the same property. Eg to access the Subject using the Strongly typed Subject Property then using the Extended Mapi property PidTagSubject. To put that another way its just two ways of accessing the same piece of stored data.
Accessing an Items data
When you access an Item with EWS your getting data from one or more properties on that Item in EWS there are two operations you can use do to this. FindItems will let you search for items and retrieve for example the subject,size and received data of multiple items however FindItems wont return things like the Body or the recipients on the messages for this you need to use a GetItem request which is abstracted as the Load Method in the Managed API or if you want to do this on multiple messages you can use LoadPropertiesForItems which does a batch GetItem request. Lets look at some examples
This first example will show the Last 10 Items in your Inbox and list the Received Date, Subject and Size using FindItems
This second example shows how to enumerate every Item in a folder you should page through 1000 items at a time to cater for throttling for example
As i mentioned before there are a number of properties such as the message recipients and the message body that would not be returned by the following examples. So if we where looking at the first example where the last 10 Items in a mailbox are returned if you wanted to show the recipients these Messages where sent to you would need to do a GetItem request on each message. This could be achieved using the Load Method on each item but a better method is to use the LoadItemsFromProperties method which will batch the GetItem requests together. An example of this looks like
Searching for Items
Generally when your dealing with user mailboxes your going to be accessing folders that have a lot of Items, users are notoriously bad at managing their own data so you may end up with folders with 20-100 thousand items so being able to effectively search and filter items is very important. Exchange offers a number of different methods to search and filter data which I've written about in the past in http://msdn.microsoft.com/en-us/library/hh148195%28v=exchg.140%29.aspx so I wont go back over this. But I'll show a number of examples that use AQS to search the content index on a Exchange server.
In the first example it will show all the emails that have a PDF attachment in the Inbox and what those attachments are called paged 100 at a time.
There are a lot of combinations that will work with AQS filters some of which I've shown before in other posts
http://gsexdev.blogspot.com.au/2010/08/using-exchange-search-and-aqs-with-ews.html . To use any of these examples with the following above sample just change the AQSString
eg to search for Items between 5-10 MB
"System.Size:5mb..10mb"
Or From a particular users
"System.Message.FromAddress:(fred@onmicrosoft.com OR amanda@onmicrosoft.com)"
With a specific Subject
"System.Subject:FootBall"
The other way of searching for Items is to use SearchFilters which offer the greatest flexibility in terms of what you can search against but come at the cost of having to build a temporary restriction on the folder you search which depending on the number of Items in the folder and the complexity of the search may work well or not.
There are a few different types of Search filters basic logic operator type filters like isEqual, Greatorthan and Less than give standard type functionality. One example of using one of these is to search for Mail that arrived Today
One type of useful searchFilter is the Not searchfilter which basically allows you to negate any SearchFilter you create for example if you want to show any Items in the Inbox that weren't IPM.Note (normal email message) you could use
Calendar Items
Calendar Items are one of the more complex types in Exchange because of the functionality that surrounds appointments such as having recurring Appointments, exceptions, and deleted exceptions. With a recurring Appointment you have one underlying master object and the recurrence pattern is stored as a property on the Master Item and exceptions are stored as hidden attachments. Because of this reason when your accessing Calendar Items you need to use Calendar Paging which tells exchange to expand the recurring appointment so what is returned to you is a separate object for each meeting occurrence. For example to show you appointment for the next week
Binding to Items Directly
To Bind to an Item directly you need to know its EWSid while this is a uniqueid it does change if the items are moved/copied between folders so as a general rule this is not a good Identifier to store externally. EWS does offer the ability to convert other identifiers to a EWSid which allows some interoperability between a Mapi EntryID from OOM/RDO or CDO code or from the Exchange Management Shell cmdlet which return different types of identifiers for example the Public Folder cmdlets will return the HexEntryID for folder which you can then convert to EWSid.
An Example of converting a HexEntryID to EWSid looks like
Once you have this Id you can then just Bind to an Item for example to bind to an Email
Different Folder Collections
Folders in Exchange has 3 collections where Items can be stored firstly you have the normal Items collection where all the normal items are stored. The Associated Items collection where the folders FAI (Folder Associated Items) are stored and the Deleted Items collection which is the dumpster v1 of the folder in Exchange 2010 this works a little different if you have Single Item Recovery enabled and dumpster v2 is in operation
The Associated Items Collection is useful if your going to be working on scripts that modify configuration data or if you want to store a custom item of your own that is hidden from the user. An example of a script that enumerates every Item in the FAI collection of the Inbox looks like
That's it for Part 1 of Items in the next post I'll cover all the operations you can perform on Items such as Creating, Copying, Moving, Deleting, Importing and Exporting.
Items, Objects and Properties
Some of the Items you might find in an Exchange Mailbox include Email, Contacts, Calendar Appointments, Meeting, Tasks and other custom Item types. The data that constitutes an Exchange Item for example the Subject of an email message is stored as one or more properties in the Exchange Mail-Store associated with that Item. Looking at the Subject example a little more deeply the underlying Property for the subject is PidTagSubject http://msdn.microsoft.com/en-us/library/cc815720.aspx . This sounds easy but with Exchange Items there aren't just one level of properties for example an Email will generally have a collection of properties for the Item, then every recipient is a separate object with its own properties and then possible one more attachments with their own properties on each attachment.
This maybe a little too much detail but it is important to consider when you need to do more complex things with EWS for the most part the Managed API abstracts out these underlying properties into Strongly type objects for example EmailMessage, Contact or Appointment. For a quick example lets look at two ways of accessing the same property. Eg to access the Subject using the Strongly typed Subject Property then using the Extended Mapi property PidTagSubject. To put that another way its just two ways of accessing the same piece of stored data.
- #Define Property Set
- $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $PidTagSubject = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0037,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
- $psPropertySet.Add($PidTagSubject)
- $ItemId = New-Object Microsoft.Exchange.WebServices.Data.ItemId($ewsid.UniqueId)
- $msMessage = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$ItemId,$psPropertySet)
- "Strong Type : " + $msMessage.Subject
- $SubjectVal = $null
- if($msMessage.TryGetProperty($PidTagSubject,[ref]$SubjectVal)){
- "Extended Property : " + $SubjectVal
- }
When you access an Item with EWS your getting data from one or more properties on that Item in EWS there are two operations you can use do to this. FindItems will let you search for items and retrieve for example the subject,size and received data of multiple items however FindItems wont return things like the Body or the recipients on the messages for this you need to use a GetItem request which is abstracted as the Load Method in the Managed API or if you want to do this on multiple messages you can use LoadPropertiesForItems which does a batch GetItem request. Lets look at some examples
This first example will show the Last 10 Items in your Inbox and list the Received Date, Subject and Size using FindItems
- #Bind to the Inbox
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define ItemView to retrive just 10 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(10)
- $fiItems = $service.FindItems($Inbox.Id,$ivItemView)
- foreach($Item in $fiItems.Items){
- "RecivedDate : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- "Size : " + $Item.Size
- }
- #Bind to Inbox
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define ItemView to retrive just 1000 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $fiItems = $null
- do{
- $fiItems = $service.FindItems($Inbox.Id,$ivItemView)
- foreach($Item in $fiItems.Items){
- "RecivedDate : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- "Size : " + $Item.Size
- }
- $ivItemView.Offset += $fiItems.Items.Count
- }while($fiItems.MoreAvailable -eq $true)
- #Bind to Inbox
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define the properties to get
- $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- #Define ItemView to retrive just 10 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(10)
- $fiItems = $service.FindItems($Inbox.Id,$ivItemView)
- [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
- foreach($Item in $fiItems.Items){
- "RecivedDate : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- "Size : " + $Item.Size
- "Recipients : " + $Item.ToRecipients
- }
Generally when your dealing with user mailboxes your going to be accessing folders that have a lot of Items, users are notoriously bad at managing their own data so you may end up with folders with 20-100 thousand items so being able to effectively search and filter items is very important. Exchange offers a number of different methods to search and filter data which I've written about in the past in http://msdn.microsoft.com/en-us/library/hh148195%28v=exchg.140%29.aspx so I wont go back over this. But I'll show a number of examples that use AQS to search the content index on a Exchange server.
In the first example it will show all the emails that have a PDF attachment in the Inbox and what those attachments are called paged 100 at a time.
- #Bind to Inbox
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define the properties to get
- $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- #Define AQS Search String
- $AQSString = "System.Message.AttachmentContents:*.pdf"
- #Define ItemView to retrive just 1000 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $fiItems = $null
- do{
- $fiItems = $service.FindItems($Inbox.Id,$AQSString,$ivItemView)
- [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
- foreach($Item in $fiItems.Items){
- "Subject : " + $Item.Subject
- foreach($Attachment in $Item.Attachments){
- if($Attachment.Name.Substring($Attachment.Name.Length-4).ToLower() -eq ".pdf"){
- $Attachment.Name
- }
- }
- }
- $ivItemView.Offset += $fiItems.Items.Count
- }while($fiItems.MoreAvailable -eq $true)
eg to search for Items between 5-10 MB
"System.Size:5mb..10mb"
Or From a particular users
"System.Message.FromAddress:(fred@onmicrosoft.com OR amanda@onmicrosoft.com)"
With a specific Subject
"System.Subject:FootBall"
The other way of searching for Items is to use SearchFilters which offer the greatest flexibility in terms of what you can search against but come at the cost of having to build a temporary restriction on the folder you search which depending on the number of Items in the folder and the complexity of the search may work well or not.
There are a few different types of Search filters basic logic operator type filters like isEqual, Greatorthan and Less than give standard type functionality. One example of using one of these is to search for Mail that arrived Today
- #Bind to Inbox
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define the properties to get
- $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,[system.DateTime]::Now.AddDays(-1));
- #Define ItemView to retrive just 1000 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $fiItems = $null
- do{
- $fiItems = $service.FindItems($Inbox.Id,$SearchFilter,$ivItemView)
- #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
- foreach($Item in $fiItems.Items){
- "Received : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- }
- $ivItemView.Offset += $fiItems.Items.Count
- }while($fiItems.MoreAvailable -eq $true)
- #Bind to Inbox
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define the properties to get
- $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note");
- $NotFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+Not($SearchFilter)
- #Define ItemView to retrive just 1000 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $fiItems = $null
- do{
- $fiItems = $service.FindItems($Inbox.Id,$NotFilter,$ivItemView)
- #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
- foreach($Item in $fiItems.Items){
- "Received : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- "ItemClass : " + $Item.ItemClass
- }
- $ivItemView.Offset += $fiItems.Items.Count
- }while($fiItems.MoreAvailable -eq $true)
Calendar Items are one of the more complex types in Exchange because of the functionality that surrounds appointments such as having recurring Appointments, exceptions, and deleted exceptions. With a recurring Appointment you have one underlying master object and the recurrence pattern is stored as a property on the Master Item and exceptions are stored as hidden attachments. Because of this reason when your accessing Calendar Items you need to use Calendar Paging which tells exchange to expand the recurring appointment so what is returned to you is a separate object for each meeting occurrence. For example to show you appointment for the next week
- #Bind to Calendar
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)
- $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define Date to Query
- $StartDate = new-object [System.DateTime]::Now
- $EndDate = new-object [System.DateTime]::Now.AddDays(7)
- #Define the calendar view
- $CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate,1000)
- $fiItems = $service.FindAppointments($Calendar.Id,$CalendarView)
- foreach($Item in $fiItems.Items){
- "Start : " + $Item.Start
- "Subject : " + $Item.Subject
- }
To Bind to an Item directly you need to know its EWSid while this is a uniqueid it does change if the items are moved/copied between folders so as a general rule this is not a good Identifier to store externally. EWS does offer the ability to convert other identifiers to a EWSid which allows some interoperability between a Mapi EntryID from OOM/RDO or CDO code or from the Exchange Management Shell cmdlet which return different types of identifiers for example the Public Folder cmdlets will return the HexEntryID for folder which you can then convert to EWSid.
An Example of converting a HexEntryID to EWSid looks like
- $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId
- $aiItem.Mailbox = $MailboxName
- $aiItem.UniqueId = "00000000EA21EB937E13F24195E1C55F12CB95B60100C34C9620940D4548BAC97277B49175AD0000000DE0040000"
- $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId;
- $ewsid = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId)
- $ewsid
- $ItemId = New-Object Microsoft.Exchange.WebServices.Data.ItemId($ewsid.UniqueId)
- $msMessage = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$ItemId)
Folders in Exchange has 3 collections where Items can be stored firstly you have the normal Items collection where all the normal items are stored. The Associated Items collection where the folders FAI (Folder Associated Items) are stored and the Deleted Items collection which is the dumpster v1 of the folder in Exchange 2010 this works a little different if you have Single Item Recovery enabled and dumpster v2 is in operation
The Associated Items Collection is useful if your going to be working on scripts that modify configuration data or if you want to store a custom item of your own that is hidden from the user. An example of a script that enumerates every Item in the FAI collection of the Inbox looks like
- #Bind to Inbox
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Define ItemView to retrive just 1000 Items
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- #Define Assoicated Traversal
- $ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated;
- $fiItems = $null
- do{
- $fiItems = $service.FindItems($Inbox.Id,$ivItemView)
- #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
- foreach($Item in $fiItems.Items){
- "Received : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- "ItemClass : " + $Item.ItemClass
- }
- $ivItemView.Offset += $fiItems.Items.Count
- }while($fiItems.MoreAvailable -eq $true)
That's it for Part 1 of Items in the next post I'll cover all the operations you can perform on Items such as Creating, Copying, Moving, Deleting, Importing and Exporting.