Saturday, February 11, 2012

EWS Managed API and Powershell How-To series Part 3 - Items

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.

  1. #Define Property Set  
  2. $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
  3. $PidTagSubject = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0037,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);  
  4. $psPropertySet.Add($PidTagSubject)  
  5.   
  6. $ItemId = New-Object Microsoft.Exchange.WebServices.Data.ItemId($ewsid.UniqueId)  
  7. $msMessage = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$ItemId,$psPropertySet)    
  8. "Strong Type : " + $msMessage.Subject  
  9. $SubjectVal = $null  
  10. if($msMessage.TryGetProperty($PidTagSubject,[ref]$SubjectVal)){  
  11.     "Extended Property : " + $SubjectVal  
  12. }  
 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

  1. #Bind to the Inbox  
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)   
  3. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  4.  
  5. #Define ItemView to retrive just 10 Items  
  6. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(10)  
  7.   
  8. $fiItems = $service.FindItems($Inbox.Id,$ivItemView)  
  9. foreach($Item in $fiItems.Items){  
  10.     "RecivedDate : " + $Item.DateTimeReceived   
  11.     "Subject     : " + $Item.Subject   
  12.     "Size        : " + $Item.Size  
  13. }  
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

  1. #Bind to Inbox  
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)   
  3. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  4.  
  5. #Define ItemView to retrive just 1000 Items  
  6. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)  
  7. $fiItems = $null  
  8. do{  
  9.     $fiItems = $service.FindItems($Inbox.Id,$ivItemView)  
  10.     foreach($Item in $fiItems.Items){  
  11.         "RecivedDate : " + $Item.DateTimeReceived   
  12.         "Subject     : " + $Item.Subject   
  13.         "Size        : " + $Item.Size  
  14.     }  
  15.     $ivItemView.Offset += $fiItems.Items.Count  
  16. }while($fiItems.MoreAvailable -eq $true)  
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

  1. #Bind to Inbox  
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)   
  3. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  4.   
  5. #Define the properties to get  
  6. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
  7. #Define ItemView to retrive just 10 Items  
  8. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(10)  
  9. $fiItems = $service.FindItems($Inbox.Id,$ivItemView)  
  10. [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
  11. foreach($Item in $fiItems.Items){  
  12.     "RecivedDate : " + $Item.DateTimeReceived   
  13.     "Subject     : " + $Item.Subject   
  14.     "Size        : " + $Item.Size  
  15.     "Recipients  : " + $Item.ToRecipients  
  16. }  
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.

  1. #Bind to Inbox    
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  3. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  4.    
  5. #Define the properties to get    
  6. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  7. #Define AQS Search String  
  8.   
  9. $AQSString = "System.Message.AttachmentContents:*.pdf"  
  10.   
  11. #Define ItemView to retrive just 1000 Items    
  12. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)    
  13. $fiItems = $null    
  14. do{    
  15.     $fiItems = $service.FindItems($Inbox.Id,$AQSString,$ivItemView)    
  16.     [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
  17.     foreach($Item in $fiItems.Items){      
  18.         "Subject     : " + $Item.Subject  
  19.         foreach($Attachment in $Item.Attachments){  
  20.             if($Attachment.Name.Substring($Attachment.Name.Length-4).ToLower() -eq ".pdf"){  
  21.                 $Attachment.Name  
  22.             }  
  23.         }  
  24.     }    
  25.     $ivItemView.Offset += $fiItems.Items.Count    
  26. }while($fiItems.MoreAvailable -eq $true)    
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

  1. #Bind to Inbox    
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  3. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  4.    
  5. #Define the properties to get    
  6. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  7.   
  8. $SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,[system.DateTime]::Now.AddDays(-1));  
  9.   
  10. #Define ItemView to retrive just 1000 Items    
  11. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)    
  12. $fiItems = $null    
  13. do{    
  14.     $fiItems = $service.FindItems($Inbox.Id,$SearchFilter,$ivItemView)    
  15.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
  16.     foreach($Item in $fiItems.Items){      
  17.         "Received    : " + $Item.DateTimeReceived  
  18.         "Subject     : " + $Item.Subject  
  19.           
  20.     }    
  21.     $ivItemView.Offset += $fiItems.Items.Count    
  22. }while($fiItems.MoreAvailable -eq $true)   
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

  1. #Bind to Inbox    
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  3. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  4.    
  5. #Define the properties to get    
  6. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  7.   
  8. $SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note");  
  9. $NotFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+Not($SearchFilter)  
  10. #Define ItemView to retrive just 1000 Items    
  11. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)    
  12. $fiItems = $null    
  13. do{    
  14.     $fiItems = $service.FindItems($Inbox.Id,$NotFilter,$ivItemView)    
  15.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
  16.     foreach($Item in $fiItems.Items){      
  17.         "Received    : " + $Item.DateTimeReceived  
  18.         "Subject     : " + $Item.Subject  
  19.         "ItemClass   : " + $Item.ItemClass  
  20.     }    
  21.     $ivItemView.Offset += $fiItems.Items.Count    
  22. }while($fiItems.MoreAvailable -eq $true)    
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


  1. #Bind to Calendar    
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)     
  3. $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  4.   
  5. #Define Date to Query 
  6. $StartDate = new-object [System.DateTime]::Now  
  7. $EndDate = new-object [System.DateTime]::Now.AddDays(7)  
  8.   
  9. #Define the calendar view  
  10. $CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate,1000)    
  11. $fiItems = $service.FindAppointments($Calendar.Id,$CalendarView)    
  12. foreach($Item in $fiItems.Items){      
  13.     "Start    : " + $Item.Start  
  14.     "Subject     : " + $Item.Subject  
  15. }    
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

  1. $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId  
  2. $aiItem.Mailbox = $MailboxName  
  3. $aiItem.UniqueId = "00000000EA21EB937E13F24195E1C55F12CB95B60100C34C9620940D4548BAC97277B49175AD0000000DE0040000"  
  4. $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId;  
  5. $ewsid = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId)  
  6. $ewsid  
Once you have this Id you can then just Bind to an Item for example to bind to an Email

  1. $ItemId = New-Object Microsoft.Exchange.WebServices.Data.ItemId($ewsid.UniqueId)
  2. $msMessage = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$ItemId)
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

  1. #Bind to Inbox    
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  3. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  4.   
  5. #Define ItemView to retrive just 1000 Items    
  6. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)   
  7. #Define Assoicated Traversal  
  8. $ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated;  
  9. $fiItems = $null    
  10. do{    
  11.     $fiItems = $service.FindItems($Inbox.Id,$ivItemView)    
  12.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
  13.     foreach($Item in $fiItems.Items){      
  14.         "Received    : " + $Item.DateTimeReceived  
  15.         "Subject     : " + $Item.Subject  
  16.         "ItemClass   : " + $Item.ItemClass  
  17.     }    
  18.     $ivItemView.Offset += $fiItems.Items.Count    
  19. }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.

26 comments:

Nuno Mota said...

As always, really, really good!! Thank you very much!

Andy Grogan said...

Hiya Glen, this is great stuff - I have to confess that I am being slightly lazy in asking this, but is there a way for the item script to cycle through sub-folders of the Inbox?
Cheers
Andy G

Glen said...

You need to use something like

$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)

$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

$findFolderResults = $tfTargetFolder.FindFolders($fvFolderView)


foreach($folder in $findFolderResults.Folders){
if($folder.TotalCount -gt 0){
"Processing Folder " + $folder.DisplayName
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$findItemsResults = $null
do{
$findItemsResults = $folder.FindItems($ivItemView)
foreach($itItem in $findItemsResults.Items){
$itItem.Subject
}
$ivItemView.offset += $findItemsResults.Items.Count
}while($findItemsResults.MoreAvailable -eq $true)
}
}

Andy Grogan said...

Awesome, cheer Glen - exactly what I need.
Cheers
A

GeorgeD said...

I keep getting the following error when i try to bind. any clue why? i'm using the 1.2 version of the API

Exception calling "Bind" with "2" argument(s): "The request failed. The underlying connection was closed: An unexpected error occurred on a send."
At line:38 char:60
+ $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind <<<< ($service,$folderid)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

Glen Scales said...

This is a problem with delegates in the managed API version 1.2 to fix this make sure you close all you existing Powershell sessions.

Remove the following line

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

Add this block in its place

## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null

$TASource=@'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'@
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly

## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll

## end code from http://poshcode.org/624

And that should fix it

Cheers
Glen

sendi said...

Thank you Glen, this is very helpful.

Do you have example of loading a message body ( with all it's properties i.e. rich text , inline image, attachment -- Apparently body could be combination of plain/rich text and html , but is not pure HTML or RTF. so parsing becomes very complex )?

I need to send only message body to the printer ( but can't loose inline images/media and create url for attachments)

Glen Scales said...

EWS will only ever give you the HTML or Text version of the Body. Not sure what's happening but have you just tried viewing the HTML body first. EWS will allways default to giving you the HTML body by default. If there is no current HTML body eg a RTF message the Store will do a on the fly conversion and give you a HTML body.

Cheers
Glen

Laurent said...

Hi Glen,
I am a beginner in powershell and EWS.

I would like to delete all appointments. I do this with the following function:
How can I simply remove all occurrences of an appointment in a single time?

Do you have any idea?
Best regard


function Delete-Appointments ($CalFolder,[int]$YearStart,[int]$YearEnd,$log)
{
$iStart=$YearStart
Write-Output "*********************Delete Calendar Appointement"
ADD-content $log -value "*********************Delete Calendar Appointement "

do
{
$StartDate = get-date -Year $iStart -Day 1 -Month 1 -Hour 0
$EndDate = get-date -Year ($iStart+1) -Day 1 -Month 1 -Hour 0


#Define the calendar view
$CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate)



#$cvCalendarview.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)

$frCalendarResult = $CalFolder.FindAppointments($Calendarview)
$entryFound = $False
foreach ($appointment in $frCalendarResult.Items)
{
ADD-content $log -value "Deleted : $($appointment.Subject)"
ADD-content $log -value "$($appointment.Uid)"
ADD-content $log -value "$($appointment.start)"
ADD-content $log -value "$($appointment.end)"
ADD-content $log -value "********************************"
Write-Host "Deleted : $($appointment.Uid)"
Write-Host "$($appointment.Subject)"
Write-Host "$($appointment.start)"
Write-Host "$($appointment.end)"
Write-Host "***************************"
$appointment.Delete('HardDelete')
}
$iStart=$iStart+1
}
until ($iStart -gt $YearEnd)

}

Glen Scales said...

I'm not quite sure what your asking do you just want to delete occurrence of one recurring appoint or all appointment in a time period. What you can do is use the appointmentType to identify and occurance from the master instance see http://msdn.microsoft.com/en-us/library/exchange/dd633700(v=exchg.80).aspx

Cheers
Glen

Laurent said...

hi glen,

Now, I try to do this and it does work.
Ews for Powershell is not really documented...

if($appointment.IsRecurring)
{
$recurringMaster = $Appointment.BindToRecurringMaster($ExService, {New-Object Microsoft.Exchange.WebServices.Data.ItemId(sOccurrenceIdOrExceptionId)});
$recurringMaster.Delete('HardDelete','SendToNone');
}else
{

$appointment.Delete('HardDelete','SendToNone');

}

Error :
[Microsoft.Exchange.WebServices.Data.Appointment] doesn't contain a method named 'BindToRecurringMaster'

If you have an idea ... I run the script in the Exchange console.

Thank you for your blog
Laurent (Belgium)

Laurent said...

I just want to delete all appointments (recurring or not) in a calendar folder.

Glen Scales said...

Then use FindItems instead of FindAppointments, this will only return single appointment and master instances. Or the other method is just use the Empty method on the Calendar folder which will remove everything in the folder

There is no method for BindToRecurringMaster on the appointment object you need to use it like
[Microsoft.Exchange.WebServices.Data.Appointment]::BindToRecurringMaster($service,$Appointment.Id)

Cheers
Glen

Laurent said...

Hi glen,
Thank you for your answer.
I had already tested empty method:

Exception calling "Empty" with "2" argument(s): "Emptying the calendar folder or search folder isn't permitted."
At D:\Scripts\Laurent\Add-UCLSunGesCalendar.ps1:890 char:9
+ $CalendarFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]:: ...
+ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ServiceResponseException


Simon Berg said...

Hi Glen

I've got all my appointments out of my calendar and stored in a CalendarView. But i cant read the Category Color of an appointment. How do i do that?

Glen Mark Martin said...

Having issues getting TryGetProperty to work for retrieving extended properties. I'm trying to write a script to use the EWS GetDelegates() call (which works fine) and compare the results with what I get by looking at all the places where delegate information is actually stored to look for broken delegations. Part of this is checking certain MAPI properties on NON_IPM_SUBTREE/Freebusy Data/LocalFreebusy.EML. I can successfully bind to the NON_IPM_SUBTREE root, search for the "Freebusy Data" folder, and search that for the the actual Freebusy object. But when I bind to that, I can't get any extended properties out of it.


$psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$PidTagScheduleInfoDelegateNames = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6844,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$psPropset.Add($PidTagScheduleInfoDelegateNames)



$Item = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$MID)

$Item.Load($psPropset)


"Let's see the delegate names:"
$PR_DELEGATES_DISPLAY_NAMES = $null
if($Item.TryGetProperty($PidTagScheduleInfoDelegateNames,[ref]$PR_DELEGATES_DISPLAY_NAMES)){
"PR_DELEGATES_DISPLAY_NAMES : " + $PR_DELEGATES_DISPLAY_NAMES }
else {write-host "TryGetProperty for PR_DELEGATES_DISPLAY_NAMES failed!!" -ForegroundColor Red
}


The output I get is "TryGetProperty for PR_DELEGATES_DISPLAY_NAMES failed!!" Nothing is added to the $ERROR variable by the failure.

Note that here I've done an explicit BIND to the object and tried a property LOAD. I've also attempted a bulk LOAD to the list of search results using the command you illustrated above:
[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)

Same result.

Any clue what I'm doing wrong on trying to retrieve these properties?

Glen Scales said...

Your property definition is wrong this is multivalued string property http://msdn.microsoft.com/en-us/library/ee237137(v=exchg.80).aspx and you have set the datatype wrong it should be

$PidTagScheduleInfoDelegateNames = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6844,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::StringArray)

Also take a look at http://blogs.msdn.com/b/emeamsgdev/archive/2014/05/16/powershell-clean-mailbox-delegates-update.aspx which has the proper defs

Cheers


Glen Mark Martin said...

Thank you kindly, sir!

Anonymous said...

Hi Glen,

You showed us how to traverse items under Folder Associated Items.
Is there a way to create a message under Folder Associated Items with EWS?

Anonymous said...

Nevermind, we can set IsAssociated = true using EmailMessage.

Anonymous said...

Hi Glen, The link you have mentioned on the various methods of searching mailbox is being removed from Microsoft. Can you help me with the various methods available.

Anonymous said...

I’m looking into the EWS FindItems method and I noticed this…Note the note section. It seems AQS uses indexes whereas search filters do not. Now, I’m wondering if AQS is old school and one should not use AQS. Do you have any thoughts on this. Basically, I’m researching for performance, the fastest way to access exchange using EWS.

<<
We recommend that you use Advanced Query Syntax (AQS) with the QueryString element to perform searches. In many cases, you can perform the same search by using the QueryString element that you can perform by using search filters. QueryString element searches are performed against a content index. Searches performed by using search filters, also called search restrictions, create a dynamic search folder that persists in memory for a short period of time. These in-memory search folders use more system resources than content index searches. For more information about using AQS search and EWS, see Searching an Exchange mailbox by using the EWS Managed API 2.0.
>>

https://msdn.microsoft.com/en-us/library/dd633659(v=exchg.80).aspx

Glen Scales said...

AQS is still the best thing to use for searching. On 2013 you also have eDiscovery which is better again in that you can now search the whole mailbox. Otherwise look at the Exchange Management Shell and the Search-Mailbox cmdlets

Anonymous said...

Great post. How would you search for calendar items with a specific string in their message body? Thanks.

Manfred Preissner said...

Hello Mr Glen this Blog is awesome!
could you please discribe to me how to
access the deleted Items of a known Publicfolder to restore them back and delete the copy from Dumpster afterwards?
Best Regards

Manfred Preissner

Anonymous said...

Hi Glen, I've used EWS API for folder creation/deletion but I'm stuck on trying to hide a particular folder using the extended property process.

The folder is at the root, but I'd like to be able specify a folder at any location.

Any help would be appricated.

thanks.