Wednesday, February 29, 2012

HowTo Series Sample 2 - Accessing System Public Folders : OAB Stats script

The following script demonstrates how you can access System Level Public Folders (Non_IPM_Subtree) using the EWS Managed API and shows how to report on the status of the OAB (Offline Address Book) distribution folders. (Note on 2010 you can also do this with the Exchange Management Shell). It produces a CSV report like

The main script is based around the how to series template but because there is no enumeration to get to the Non_IPM_Subtree one trick is needed. The first thing is to bind to the normal Public Folder Root using PublicFoldersRoot Enum then use the ParentFolderId property of this folder to bind to the parent then search  for the Non_IPM_Subtree folder which will be a subfolder of this folder. Then you can query for any System Folder from this point in this example it finds the OAB folder queries for any subfolders under this folder and then finds any of these subfolders with Items and queries the size, number of Items and the Modified date of the last item.

I've put a download of this script here the script itself looks like

  1. ## EWS Managed API Connect Script  
  2. ## Requires the EWS Managed API and Powershell V2.0 or greator    
  4. ## Load Managed API dll    
  5. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"    
  7. ## Set Exchange Version    
  8. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1    
  10. ## Create Exchange Service Object    
  11. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  13. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  15. #Credentials Option 1 using UPN for the windows Account    
  16. $creds = New-Object System.Net.NetworkCredential("","password")     
  17. $service.Credentials = $creds        
  19. #Credentials Option 2    
  20. #service.UseDefaultCredentials = $true    
  22. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  24. [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}    
  26. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
  28. #CAS URL Option 1 Autodiscover    
  29. $service.AutodiscoverUrl("",{$true})    
  30. "Using CAS Server : " + $Service.url     
  32. #CAS URL Option 2 Hardcoded    
  34. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  35. #$service.Url = $uri      
  37. ## Optional section for Exchange Impersonation    
  39. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, "")    
  42. #Define the FolderSize Extended Property  
  43. $PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)  
  44. $Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
  45. $Propset.add($PR_MESSAGE_SIZE_EXTENDED)  
  47. $PFRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)  
  48. $NonIPMPfRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$PFRoot.ParentFolderId)  
  49. $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)  
  50. $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"NON_IPM_SUBTREE")  
  51. $folders = $NonIPMPfRoot.Findfolders($sfSearchFilter,$fvFolderView)  
  52. foreach($folder in $folders.Folders){  
  53.     #$folder   
  54.     $sfSearchFilter1 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"OFFLINE ADDRESS BOOK")  
  55.     $fvFolderView1 =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)  
  56.     $fvFolderView1.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;  
  57.     $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)  
  58.     $OABFolder = $folder.Findfolders($sfSearchFilter1,$fvFolderView1).Folders[0]  
  59.     $OABFolders = $OABFolder.Findfolders($fvFolderView1)  
  60.     foreach($OABSubFolder in $OABFolders.Folders){  
  61.         if($OABSubFolder.ChildFolderCount -gt 0){  
  62.             $OABSubFolder.DisplayName  
  63.             $fvFolderView2 =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)  
  64.             $fvFolderView2.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;  
  65.             $fvFolderView2.PropertySet = $Propset  
  66.             $SubFolders = $OABSubFolder.Findfolders($fvFolderView2)  
  67.                 foreach($SubFolder in $SubFolders.Folders){  
  68.                 $rptObj = "" | select  RootFolderName,SubFolderName,FolderItemCount,FolderSize,NewestItemLastModified  
  69.                 $rptObj.RootFolderName = $OABSubFolder.DisplayName  
  70.                 $rptObj.SubFolderName = $SubFolder.DisplayName  
  71.                 $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)  
  72.                 $FindItems = $SubFolder.FindItems($ivItemView)  
  73.                 $rptObj.FolderItemCount = $FindItems.Items.Count  
  74.                 if($FindItems.Items.Count -gt 0){  
  75.                     $rptObj.NewestItemLastModified = $FindItems.Items[0].LastModifiedTime.ToString()  
  76.                 }  
  77.                 $folderSize = $null  
  78.                 if($SubFolder.TryGetProperty($PR_MESSAGE_SIZE_EXTENDED, [ref]$folderSize)){  
  79.                     $rptObj.FolderSize = [MATH]::Round($folderSize/1024,0)  
  80.                 }  
  82.                 $rptCollection += $rptObj  
  83.             }  
  85.         }  
  88.     }  
  89. }  
  90. $rptCollection | Export-Csv -NoTypeInformation c:\temp\OabStatReports.csv  

Monday, February 27, 2012

HowTo Series Sample 1 - Unused Mailbox report for Exchange 2010

The is the first of my samples based on the EWS Managed API and Powershell how to series I've been writing. This one is actually an update to a script i wrote a few years ago to find unused mailboxes on Exchange 2007. This script is designed to be run from within a Remote Powershell session that is already connected to your Exchange 2010 org (or from a Exchange Online/Office365 remote powershell session).

 It uses Get-Mailbox to get all the mailboxes in your org and then it uses the EWS Managed API to connect to each mailbox and makes a query of the Inbox (using a AQS query String) to get all the Email in the mailbox for the last 6 months,  then it queries for all email in the last 6 months that is still marked unread. Then it queries the Sent Email folder for how many email where sent for the last 6 months. (this would only be accurate based on the user behavior of moving messages). Its combines this with the statistics from Get-MailboxStatistics to produce a csv file that would give you a report that looks like

If you want to understand how it works have a read of the How To Series posts and hopefully you should be able to work out how to customize it if you need to for your own environment. The Script as posted uses EWS Impersonation

If you want to customize which mailboxes it reports on then just change the Get-Mailbox line

Get-Mailbox -ResultSize Unlimited | ForEach-Object{  

eg if you want to limit to only checking one server you could use

Get-Mailbox -ResultSize Unlimited -Server servernameblah | ForEach-Object{

You could do similar with other filter properties such as Database or OU

I've posted a downloadable version here

The script itself looks like

  1. ## EWS Managed API Connect Module Script written by Glen Scales  
  2. ## Requires the EWS Managed API and Powershell V2.0 or greator  
  3. $RptCollection = @()  
  4. ## Load Managed API dll  
  5. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"  
  7. ## Set Exchange Version  
  8. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1  
  10. ## Create Exchange Service Object  
  11. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)  
  13. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials  
  15. #Credentials Option 1 using UPN for the windows Account  
  16. $psCred = Get-Credential  
  17. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())  
  18. $service.Credentials = $creds      
  20. #Credentials Option 2  
  21. #service.UseDefaultCredentials = $true  
  23. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates  
  25. ## Code From  
  26. ## Create a compilation environment  
  27. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  28. $Compiler=$Provider.CreateCompiler()  
  29. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  30. $Params.GenerateExecutable=$False  
  31. $Params.GenerateInMemory=$True  
  32. $Params.IncludeDebugInformation=$False  
  33. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  35. $TASource=@' 
  36. namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  37. public class TrustAll : System.Net.ICertificatePolicy { 
  38. public TrustAll() { 
  39. } 
  40. public bool CheckValidationResult(System.Net.ServicePoint sp, 
  41. System.Security.Cryptography.X509Certificates.X509Certificate cert, 
  42. System.Net.WebRequest req, int problem) { 
  43. return true; 
  44. } 
  45. } 
  46. } 
  47. '@  
  48. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  49. $TAAssembly=$TAResults.CompiledAssembly  
  51. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  52. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  53. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  55. ## end code from  
  58. function CovertBitValue($String){  
  59.     $numItempattern = '(?=\().*(?=bytes)'  
  60.     $matchedItemsNumber = [regex]::matches($String$numItempattern)   
  61.     $Mb = [INT64]$matchedItemsNumber[0].Value.Replace("(","").Replace(",","")  
  62.     return [math]::round($Mb/1048576,0)  
  63. }  
  65. Get-Mailbox -ResultSize Unlimited | ForEach-Object{   
  66. $MailboxName = $_.PrimarySMTPAddress.ToString()  
  67. "Processing Mailbox : " + $MailboxName  
  68. if($service.url -eq $null){  
  69.     ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use  
  71.     #CAS URL Option 1 Autodiscover  
  72.     $service.AutodiscoverUrl($MailboxName,{$true})  
  73.     "Using CAS Server : " + $Service.url   
  75.     #CAS URL Option 2 Hardcoded  
  76.     #$uri=[system.URI] "https://casservername/ews/exchange.asmx"  
  77.     #$service.Url = $uri    
  78. }  
  81. $rptObj = "" | select  MailboxName,Mailboxsize,LastLogon,LastLogonAccount,Last6MonthsTotal,Last6MonthsUnread,LastMailRecieved,Last6MonthsSent,LastMailSent  
  82. $rptObj.MailboxName = $MailboxName  
  84. $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)  
  87. $Range = [system.DateTime]::Now.AddMonths(-6).ToString("MM/dd/yyyy") + ".." + [system.DateTime]::Now.ToString("MM/dd/yyyy")   
  88. $AQSString1 = "System.Message.DateReceived:" + $Range   
  89. $AQSString2 = $AQSString1 + " and isread:false"  
  90. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  91. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  93. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$MailboxName)     
  94. $SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  96. $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)  
  98. $MailboxStats = Get-MailboxStatistics $MailboxName  
  99. $ts = CovertBitValue($MailboxStats.TotalItemSize.ToString())  
  100. "Total Size : " + $MailboxStats.TotalItemSize  
  101. $rptObj.MailboxSize = $ts  
  102. "Last Logon Time : " + $MailboxStats.LastLogonTime  
  103. $rptObj.LastLogon = $MailboxStats.LastLogonTime  
  104. "Last Logon Account : " + $MailboxStats.LastLoggedOnUserAccount  
  105. $rptObj.LastLogonAccount = $MailboxStats.LastLoggedOnUserAccount  
  106. $fiResults = $Inbox.findItems($AQSString1,$ivItemView)  
  107. $rptObj.Last6MonthsTotal = $fiResults.TotalCount  
  108. "Last 6 Months : " + $fiResults.TotalCount  
  109. if($fiResults.TotalCount -gt 0){  
  110.     "Last Mail Recieved : " + $fiResults.Items[0].DateTimeReceived  
  111.     $rptObj.LastMailRecieved = $fiResults.Items[0].DateTimeReceived  
  112. }  
  113. $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)  
  114. $fiResults = $Inbox.findItems($AQSString2,$ivItemView)  
  115. "Last 6 Months Unread : " + $fiResults.TotalCount  
  116. $rptObj.Last6MonthsUnread = $fiResults.TotalCount  
  117. $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)  
  118. $fiResults = $SentItems.findItems($AQSString1,$ivItemView)  
  119. "Last 6 Months Sent : " + $fiResults.TotalCount  
  120. $rptObj.Last6MonthsSent = $fiResults.TotalCount  
  121. if($fiResults.TotalCount -gt 0){  
  122.     "Last Mail Sent Date : " + $fiResults.Items[0].DateTimeSent  
  123.     $rptObj.LastMailSent = $fiResults.Items[0].DateTimeSent  
  124. }  
  125. $RptCollection +=$rptObj  
  126. }  
  127. $RptCollection | Export-Csv -NoTypeInformation  c:\temp\unreadReport.csv  

Wednesday, February 22, 2012

EWS Managed API and Powershell How-To series Part 4 - Items Part 2

Continuing on from my last post in this how to series on Items this post is going to look at how you can go about creating, deleting, moving, copying,exporting different Items and we will also look at the important batch operations which help when you need to perform actions on multiple Items at the same time.

As I have mentioned previously in this series when your dealing with Exchange Objects your dealing with Rich data-types that vary greatly eg  If you compare the properties on an Email object with that of a Task while they share a set of common properties what makes a Task a Task is its unique properties such as Percent complete, Start Time etc which have all been dutifully documented in this Exchange Protocol document. As this is the first I've mentioned this I would strongly recommend you check out the Open Specification Development Center which is a really brilliant resource for any reference information you need about using Exchange data types or EWS in general.

Creating Items

 With EWS when you create Items you use the Strongly Typed class for that particular Item you want to create for example Email, Appointment, Tasks etc. The exception for this is when you have an object Type where this in no Strong Type such as Notes (or StickyNotes), Journals Items etc for these you use the MessageType class and then change the ItemClass property to the IPM. type you want. Lets look at a few examples the first sample shows you how to Send an Email and the Second how to create an Appointment.

  1. # Create Email Object  
  2. $EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service  
  3. #Set the Subject  
  4. $EmailMessage.Subject = "Subject of Email blah"  
  5. #Set the Body of Messsage  
  6. $EmailMessage.Body = "Message Body Blah blah"  
  7. #Add Recipients  
  8. $EmailMessage.ToRecipients.Add("")  
  9. #Send Message SendandSave will save the Message to the Sender Sent Item Folder  
  10. $EmailMessage.SendAndSaveCopy()  
Creating an Appointment

  1. ##Create a Calendar Appointment  
  3. $Appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $service  
  4. #Set Start Time  
  5. $Appointment.Start = [system.DateTime]::Now.AddDays(7)  
  6. #Set End Time  
  7. $Appointment.End = [system.DateTime]::Now.AddDays(7).AddHours(1)  
  8. #Set Subject  
  9. $Appointment.Subject = "Drink Coffee with Friend"  
  10. #Set the Location  
  11. $Appointment.Location = "Good Coffee Shop"  
  12. #Set any Notes  
  13. $Appointment.Body = "Dont forget to mention X and Y"  
  14. #Create Appointment will save to the default Calendar  
  15. $Appointment.Save()  
Okay now lets look at what to do when you need to create an Item where there isn't a Strong Type. The first thing you need to know is all the underlying Extended Mapi properties that make this Item work the most accurate source for this is the Exchange protocol documents for example for a Note this would this. The other way is to look at an example Item with a Mapi Editor like OutlookSpy or MFCMapi (personally i use both) where you can see all the underlying extended properties there's a good sample of doing this here. Here's a sample for creating a StickyNote

  1. #Create Sticky Note  
  3. #First we use the EmailMessage Class (MessageType) this ensures we can change the ItemClass  
  4. $snStickyNote = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service  
  5. #Set the Subject of the Note  
  6. $snStickyNote.Subject = "Stick Note Subject"  
  7. #Change the ItemClass to IPM.StickNote 
  8. $snStickyNote.ItemClass = "IPM.StickyNote"  
  9. #Set the Text body of the Note  
  10. $snStickyNote.Body = "First Line `r`nNext Line"  
  11. #Start Note specific Extended properties   
  12. #Define the Guid for the Notes Named properties  
  13. $noteGuid = new-object Guid("0006200E-0000-0000-C000-000000000046")  
  14. #Set the Colour of the Note  
  15. $snColour = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($noteGuid,35584, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)  
  16. #Set the Height of the Note  
  17. $snHeight = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($noteGuid,35586, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)  
  18. #Set the Width of the Note  
  19. $snWidth = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($noteGuid,35587, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)  
  20. #Postion from Left  
  21. $snLeft = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($noteGuid,35588, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)  
  22. #Postion from Top  
  23. $snTop = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($noteGuid,35589, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)  
  24. #Set Note props  
  25. $snStickyNote.SetExtendedProperty($snColour,3)  
  26. $snStickyNote.SetExtendedProperty($snHeight,200)  
  27. $snStickyNote.SetExtendedProperty($snWidth,166)  
  28. $snStickyNote.SetExtendedProperty($snLeft,80)  
  29. $snStickyNote.SetExtendedProperty($snTop,80)  
  30. #Save the Item to the Notes Folder  
  31. $snStickyNote.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Notes)  
Creating Items in Public Folders or custom Mailbox folders

To create these type of objects in Public folders there is no real difference to a normal mailbox folder other then in the save method eg $snStickyNote.Save() you need to use the EWSid of the public folder in question see Part two in the how to series for information on how to get the EWSid of the folder.

Copy and Moving Items between folders

With EWS you have the ability to move Items between folders in a mailbox or copy them between folders or between different Mailboxes the later can be useful if your looking to replicate one folder to the other. With the Managed API there is a simple Copy or Move Method on an Item that takes the EWS FolderID of the folder where you want to copy\move the Item to. For a simple sample lets look at a script that the finds a Message based on its subject then demonstrates first ly copy and then a move to a folder called destination folder located in the Inbox

  1. #Move and Copy sample  
  3. #AQSString  
  5. $AqsString = "Subject:=`"Test Mail Move`""  
  7. #Bind to Inbox    
  8. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  9. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  11. #Get the ID of the folder to move to  
  12. $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)  
  13. $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;  
  14. $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"MoveDestination")  
  15. $findFolderResults = $Inbox.FindFolders($SfSearchFilter,$fvFolderView)  
  17. #Define ItemView to retrive just 1000 Items    
  18. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)  
  19. $fiItems = $null    
  20. do{    
  21.     $fiItems = $service.FindItems($Inbox.Id,$AqsString,$ivItemView)    
  22.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
  23.     foreach($Item in $fiItems.Items){      
  24.         # Copy the Message  
  25.         $Item.Copy($findFolderResults.Folders[0].Id)  
  26.         # Move the Message  
  27.         $Item.Move($findFolderResults.Folders[0].Id)  
  28.     }    
  29.     $ivItemView.Offset += $fiItems.Items.Count    
  30. }while($fiItems.MoreAvailable -eq $true)    
This type of script works okay when you only need to copy or move one item at a time however when you want to move multiple items its better to use the Batch methods which essentially batch the individual move or copy requests into one request. This can be useful when you are doing archiving, cleanup or deletion type scripts. The following sample will create a folder under the Inbox called January2011 and then query for all the items in the Inbox from January2011 using a AQS string it will then do a batch move of these items to the January2011 folder.

  1. #Batch Move Sample   
  3. #AQSString  
  5. $Range = "01/01/2012..01/30/2012"    
  6. $AQSString = "System.Message.DateReceived:" + $Range   
  8. #Bind to Inbox    
  9. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  10. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  12. #Create the folder to move to  
  13. $NewFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service)    
  14. $NewFolder.DisplayName = "January2011"    
  15. $NewFolder.Save($Inbox.Id)    
  17. #Define ItemView to retrive just 1000 Items    
  18. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)  
  19. $fiItems = $null    
  20. do{   
  21.     #Create the collection of ItemIds to perform batch request  
  22.     $Itemids = [activator]::createinstance(([type]'System.Collections.Generic.List`1').makegenerictype([Microsoft.Exchange.WebServices.Data.ItemId]))    
  23.     $fiItems = $service.FindItems($Inbox.Id,$AQSString,$ivItemView)    
  24.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)  
  25.     foreach($Item in $fiItems.Items){   
  26.         "Processing : " + $Item.Subject  
  27.         $Itemids.add($Item.Id)  
  28.     }    
  29.     $Result = $service.MoveItems($Itemids,$NewFolder.Id)  
  30.     [INT]$Rcount = 0    
  31.     foreach ($res in $Result){    
  32.         if ($res.Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success){    
  33.             $Rcount++    
  34.         }    
  35.     }    
  36.     $Rcount.ToString() + " Items moved successfully"    
  37.     $ivItemView.Offset += $fiItems.Items.Count    
  38. }while($fiItems.MoreAvailable -eq $true)    
Deleting Items

Deleting Items works the same as Copy or Move with the addition of passing the delete mode you wish for example to delete the Item instead of copying/Moving it you would use something like

  1.         $Item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete)
There is a Batch Delete sample in part 2 of this series.

 Exporting and Importing Items

One frequently asked question about EWS is how can i export to a PST or MSG file and unfortunately the answer is you can't these are Office file formats so if you want to use them you need to use an Office API like the OOM or my recommendation would be to use Redemption/RDO (Or you can use the Exchange Management Shell and the export-... cmdlets).

However there are a few methods you can use to Export and Import Items in other formats one format you can use for Email Messages is to use the Mime content of a message which is essentially the RFC822 stream of the message (or more or less the stream you would see submitted to and from the MTA over SMTP). Using the Mime Content is only good for Email messages and it wont include any custom Mapi properties or message flags so it would not be a full fidelity export of a message. Here's a sample of how to export the last message received to an EML file.

  1. #Eml Export sample  
  3. #Define PropertySet  
  4. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ItemSchema]::MimeContent)   
  5. #Bind to the Inbox  
  6. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  7. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  8. #Set the ItemView to only return 1 Email  
  9. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)  
  10. #Define PropertySet to load MimeContent  
  11. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ItemSchema]::MimeContent)   
  12. $fiItems = $service.FindItems($folderid,$ivItemView)  
  13. foreach($itItem in $fiItems.Items){  
  14.     $itItem.load($psPropset)  
  15.     $fileName = "C:\temp\exportedmail.eml"  
  16.     $fiFile = new-object System.IO.FileStream($fileName, [System.IO.FileMode]::Create)   
  17.     $fiFile.Write($itItem.MimeContent.Content, 0,$itItem.MimeContent.Content.Length)  
  18.     $fiFile.Close()   
  19. }  
To Import an EML file you essentially upload the MIME content you either saved from the export script or other EML file from another email client. To make sure the message doesn't appear as a draft you need to set the SentFlag on the message. For example to upload an EML file

  1. #Upload Eml Sample  
  3. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  4. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  5. #Crete Email Object  
  6. $emUploadEmail = new-object Microsoft.Exchange.WebServices.Data.EmailMessage($service)  
  7. #Read File  
  8. [byte[]]$bdBinaryData1 =  get-content -encoding byte "C:\temp\exportedmail.eml"  
  9. #Set Mime Content in Message  
  10. $emUploadEmail.MimeContent = new-object Microsoft.Exchange.WebServices.Data.MimeContent("us-ascii"$bdBinaryData1);  
  11. #Set Sent Message Flags which means message wont appear as a Draft  
  12. $PR_Flags = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3591, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);  
  13. $emUploadEmail.SetExtendedProperty($PR_Flags,"1")  
  14. $emUploadEmail.Save($Inbox.Id)  
The other way you can export and Import Items with EWS on Exchange 2010 is to use the new ExportItems and UploadItems operations. These operations export items in a Opaque data stream that contains all the Store properties of the underlying Item which make its a full fidelity copy of that underlying Item. There is nothing in the Managed API to allow you to use these operations so you need to use either WSDL proxy code or raw SOAP for more information have a look at these two links


That's it for this post in the next post I'll look at the special operations in EWS to Get the OOFStatus, FreeBusy and create Inbox Rules.

Monday, February 20, 2012

Version 1.2 or the EWS Managed API just released

The new version of the EWS Managed API has just been released which means you can start using the new Exchange 2010 SP2 features that have been added to the Managed API. A Quck few examples are
First make sure you set the Version to SP2 eg

  1. ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);  
Using Password Expiration
  1. DateTime PasswordExpirDate = (DateTime)service.GetPasswordExpirationDate("");  
Accessing the Directory Photo
  1. NameResolutionCollection ncCol = service.ResolveName("",ResolveNameSearchLocation.DirectoryOnly,true);  
  2. foreach (NameResolution nc in ncCol) {  
  3.     Byte[] dirPhoto = nc.Contact.DirectoryPhoto;  
  4. }  

See a full list of the new features at

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  .  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)  
  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)  
  5. #Define ItemView to retrive just 10 Items  
  6. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(10)  
  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)  
  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)  
  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 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)    
  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  
  9. $AQSString = "System.Message.AttachmentContents:*.pdf"  
  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 . To use any of these examples with the following above sample just change the AQSString

eg to search for Items between 5-10 MB


Or From a particular users

"System.Message.FromAddress:( OR" 

With a specific Subject


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)    
  5. #Define the properties to get    
  6. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  8. $SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,[system.DateTime]::Now.AddDays(-1));  
  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  
  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)    
  5. #Define the properties to get    
  6. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  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)    
  5. #Define Date to Query 
  6. $StartDate = new-object [System.DateTime]::Now  
  7. $EndDate = new-object [System.DateTime]::Now.AddDays(7)  
  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)    
  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.