Quantcast
Channel: Glen's Exchange and Office 365 Dev Blog
Viewing all 241 articles
Browse latest View live

Using EWS and AQS to check number of Unread in X days with Graph

$
0
0
It seems that you can never have too many ways of reading the number of Unread messages in a Inbox so here is another method. The following script uses two AQS queries to work out first the number of messages in the Inbox from a prescribed period eg the last 30 days and then the number of these that are then unread. It then works out the percentage of unread using of the two values and then creates a graph to display in the Console using Alt-ASCII characters which produces a pretty nifty output eg


It also produces a CSV output eg


AQS queries will work in Exchange 2010 and 2013 so this script should work okay in those environments as well as ExchangeOnline.

I've created two versions of this script, the first version just reports on one user so you run it with the email of the users you want to report on and the number of days to query back. eg

.\AQSUsr.ps1 gscales1@msgdevelop.onmicrosoft.com 30

the second version reports on all of the users in a CSV file and uses EWS Impersonation to access the Mailboxes. To use the script you need to have configured EWS Impersonation and feed the script a CSV file of the users you want to report on eg the CSV file should look like

smtpaddress
user1@domain.com
user2@domain.com

and you run it like

.\AQSAllUsr.ps1 ./users.csv 30

I've put a download of both scripts here the code look like


  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $csvFileName = $args[0]  
  4. $DaysBack = $args[1]  
  5.   
  6. ## Load Managed API dll    
  7. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
  8.     
  9. ## Set Exchange Version    
  10. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  11.     
  12. ## Create Exchange Service Object    
  13. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  14.     
  15. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  16.     
  17. #Credentials Option 1 using UPN for the windows Account    
  18. $psCred = Get-Credential    
  19. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  20. $service.Credentials = $creds        
  21.     
  22. #Credentials Option 2    
  23. #service.UseDefaultCredentials = $true    
  24.     
  25. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  26.     
  27. ## Code From http://poshcode.org/624  
  28. ## Create a compilation environment  
  29. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  30. $Compiler=$Provider.CreateCompiler()  
  31. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  32. $Params.GenerateExecutable=$False  
  33. $Params.GenerateInMemory=$True  
  34. $Params.IncludeDebugInformation=$False  
  35. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  36.   
  37. $TASource=@' 
  38.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  39.     public class TrustAll : System.Net.ICertificatePolicy { 
  40.       public TrustAll() {  
  41.       } 
  42.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  43.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  44.         System.Net.WebRequest req, int problem) { 
  45.         return true; 
  46.       } 
  47.     } 
  48.   } 
  49. '@   
  50. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  51. $TAAssembly=$TAResults.CompiledAssembly  
  52.   
  53. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  54. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  55. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  56.   
  57. ## end code from http://poshcode.org/624  
  58.     
  59. ## 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    
  60.     
  61. #CAS URL Option 1 Autodiscover    
  62. #$service.AutodiscoverUrl($MailboxName,{$true})    
  63. #"Using CAS Server : " + $Service.url     
  64.      
  65. #CAS URL Option 2 Hardcoded    
  66.     
  67. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  68. #$service.Url = $uri      
  69.     
  70. ## Optional section for Exchange Impersonation    
  71.     
  72.   
  73. $Script:rptCollection = @()  
  74. function Process-Mailbox{    
  75.     param (    
  76.             $SmtpAddress = "$( throw 'SMTPAddress is a mandatory Parameter' )"    
  77.           )    
  78.     process{    
  79.         Write-Host ("Processing Mailbox : " + $SmtpAddress)    
  80.   
  81. # Bind to the Inbox Folder  
  82. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$SmtpAddress)     
  83. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  84. $Range = [system.DateTime]::Now.AddDays(-$DaysBack).ToString("MM/dd/yyyy") + ".." + [system.DateTime]::Now.AddDays(1).ToString("MM/dd/yyyy")     
  85. $AQSString1 = "System.Message.DateReceived:" + $Range     
  86. $AQSString2 = "(" + $AQSString1 + ") AND (isread:false)"   
  87. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)    
  88. $allMail = $Inbox.FindItems($AQSString1,$ivItemView)  
  89. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)   
  90. $unread = $Inbox.FindItems($AQSString2,$ivItemView)  
  91. $rptObj = "" | Select MailboxName,TotalCount,Unread,PercentUnread,PercentGraph  
  92.   
  93. #Write-Host ("All Mail " + $allMail.TotalCount)   
  94. #Write-Host ("Unread " + $unread.TotalCount)   
  95. $rptObj.MailboxName = $SmtpAddress  
  96. $rptObj.TotalCount = $allMail.TotalCount  
  97. $rptObj.Unread = $unread.TotalCount   
  98. $PercentUnread = 0  
  99. if($unread.TotalCount -gt 0){  
  100.     $PercentUnread = [Math]::round((($unread.TotalCount/$allMail.TotalCount) * 100))  
  101. }  
  102. $rptObj.PercentUnread = $PercentUnread   
  103. #Write-Host ("Percent Unread " + $PercentUnread)  
  104. $ureadGraph = ""  
  105. for($intval=0;$intval -lt 100;$intval+=4){  
  106.     if($PercentUnread -gt $intval){  
  107.         $ureadGraph += "▓"  
  108.     }  
  109.     else{         
  110.         $ureadGraph += "░"  
  111.     }  
  112. }  
  113. #Write-Host $ureadGraph  
  114. $rptObj.PercentGraph = $ureadGraph  
  115. $rptObj | fl  
  116. $Script:rptCollection +=$rptObj  
  117.   
  118. }  
  119. }  
  120. Import-Csv -Path $csvFileName | ForEach-Object{    
  121.     if($service.url -eq $null){    
  122.         $service.AutodiscoverUrl($_.SmtpAddress,{$true})     
  123.         "Using CAS Server : " + $Service.url     
  124.     }    
  125.     Try{    
  126.         $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $_.SmtpAddress)   
  127.   
  128.         Process-Mailbox -SmtpAddress $_.SmtpAddress    
  129.     }    
  130.     catch{    
  131.         Write-host ("Error processing Mailbox : " + $_.SmtpAddress + $_.Exception.Message.ToString())    
  132.         $Error.Clear()  
  133.     }    
  134. }    
  135. $Script:rptCollection | ft  
  136. $Script:rptCollection | Export-Csv -NoTypeInformation -Path c:\temp\InboxUnreadReport.csv -Encoding UTF8   


Quota if Mailbox Size script for Exchange 2010,2013

$
0
0
If your playing around with different Mailbox quota values you might want to see what the usage of those quotas would be before you apply them to a Mailbox. This is a very simple Mailbox Size script for Remote Powershell to show you this with a funky console graph output (and a CSV report) eg


So what the script does is the normal Get-Mailbox | Get-MailboxStatistics to get the TotalSize of the Mailbox in MB and compares that against a QuotaIf Value you feed into script in the above example that was 2000 MB. It then works out the current percentage used and the produces an Alt-ASCII graph and the above table.

When you run this script you need to feed in the QuotaIf value eg

.\quotaIf.ps1 2000

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

  1. $QuotaIfVal = $args[0]  
  2. $Script:rptCollection = @()  
  3. get-mailbox -ResultSize unlimited| Get-MailboxStatistics | foreach-object{  
  4.     $rptObj = "" | Select MailboxName,TotalSize,QuotaIfPercent,PercentGraph  
  5.     $rptObj.MailboxName = $_.DisplayName  
  6.     [Int64]$rptObj.TotalSize = ($_.TotalItemSize.ToString() |%{($_.Substring($_.indexof("(")+1,$_.indexof("b")-$_.indexof("(")-2)) -replace(",","")})/1MB  
  7.   
  8.     $rptObj.QuotaIfPercent = 0    
  9.     if($rptObj.TotalSize -gt 0){  
  10.         $rptObj.QuotaIfPercent = [Math]::round((($rptObj.TotalSize/$QuotaIfVal) * 100))   
  11.     }  
  12.     $PercentGraph = ""  
  13.     for($intval=0;$intval -lt 100;$intval+=4){  
  14.         if($rptObj.QuotaIfPercent -gt $intval){  
  15.             $PercentGraph += "▓"  
  16.         }  
  17.         else{         
  18.             $PercentGraph += "░"  
  19.         }  
  20.     }  
  21.     $rptObj.PercentGraph = $PercentGraph   
  22.     $rptObj | fl  
  23.     $Script:rptCollection +=$rptObj   
  24. }  
  25. $Script:rptCollection  
  26. $Script:rptCollection | Export-Csv -NoTypeInformation -Path c:\temp\QuotaIfReport.csv -Encoding UTF8   

Oneliner Mailbox access with Powershell in Exchange Online using the oData preview

$
0
0
One of the things that was announced at MEC recently was Microsoft's Cloud first strategy with Exchange, which basically means that new feature will appear in Exchange Online first and then at some later date make their way into the OnPremise version of Exchange. One of these new cloud first features is the OData API for Mailbox data which is a REST based API for accessing mailbox data (as apposed to EWS which is a SOAP based API for accessing mailbox data). JSON and REST have become the standard for building WebAPI's over the past few years and every man and his dog (Google, Facebook,Twitter,Apple etc) are now using this so its exciting to see Exchange make this move.

If you want to find out more about it and you have a couple of hours to watch some good video's I would check the following two presentations from MEC http://channel9.msdn.com/Events/MEC/2014/EXT301 and  http://channel9.msdn.com/Events/MEC/2014/EXT304 which will give you a good grounding in both oData  and also the new consent framework which is important if your building modern apps. 

One of the interesting things your can do with these REST API is using the Invoke-RestMethod cmdlet in Powershell v4 is now get access to Mailbox data with one line of code. Here are a few samples to wet your appetite and get you started (note this API is still in preview so this is subject to change/errors and bugs)

Show Unread Inbox Messages


  1. Invoke-RestMethod -Uri "https://outlook.office365.com/ews/odata/Me/Inbox/Messages?`$filter=IsRead eq false" -Credential (get-credential) | foreach-object{$_.value | select Subject}  

Show Calendar Appointments for the next 7 days


  1. Invoke-RestMethod -Uri ("https://outlook.office365.com/ews/odata/Me/Calendar/Events?`$filter=Start le " + (Get-Date).ToUniversalTime().AddDays(7).ToString("yyyy-MM-ddThh:mm:ssZ") + " and End ge " + (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ssZ")) -Credential (Get-Credential) | foreach-object{$_.Value}  

Show Contacts


  1. Invoke-RestMethod -Uri "https://outlook.office365.com/ews/odata/Me/Inbox/Messages" -Credential (get-credential) | foreach-object{$_.value}  

There are lots more example of using the new REST commands at http://msdn.microsoft.com/en-us/library/office/dn605892(v=office.15).aspx


Slide deck from my MEC Presentation "Using exchange as a platform for innovation"

$
0
0
The slide deck from my MEC presentation "Using exchange as a platform for innovation" has just been made available on http://video.ch9.ms/sessions/mec/2014/EXTIN401_Scales.pptx  . I should have the GPX mail app posted up soon when i sort out the tile issue out. Any venture capitalist with money to loose on creating crazy/interesting/unique devices that connect to Exchange mailboxes please watch the videos in the presentation (got plenty of ideas in the pipeline) and let me know :) .

(One typo in the PPX is I managed to create a new EWS operation "FindPerson" should have been FindPeople http://msdn.microsoft.com/en-us/library/office/jj191039(v=exchg.150).aspx March was a long month)...

GPX Route Mail App for Exchange 2013 and Exchange Online

$
0
0
As part of my MEC talk last month I presented a Mail App that would show a Map with route layer added based on the content of a GPX attachment on a received message. For example here's a screenshot of the Application that was activated from a GPX file that was exported from the Google Tracks application .


Mail Apps are a new feature of Exchange2013/Exchange Online that gives you a standard way of extending the functionality of Outlook,OWA and OWA for devices. The way this particular Mail app works is first, in the Manifest file there is a filter so it will only show on messages you open/view that have an attachment eg

  1. <Rule xsi:type="RuleCollection" Mode="And">  
  2.     <Rule xsi:type="ItemIs" FormType = "Read" ItemType="Message" />  
  3.     <Rule xsi:type="ItemHasAttachment" />  
  4. </Rule>  

Next if somebody activates the application the next thing it does is uses the new Attachments API that was added in 2013 SP1 to first get the details of the attachments on the message, it then checks if one of them is a GPX attachment. If there is a GPX attachment it then uses an externally hosted controller to make the GetAttachment Call to EWS using a token obtained for authentication to get the Attachment content which is then returned to the MailApp (this is done using a modified version of the following sample). Once the Mail App has the GPX content from the attachment it then uses leaflet which is a open source javascript library that makes mapping easier and also a GPX plugin that was developed for leaflet to add a GPX layer to the map.

Note in this sample I'm using  openstreetmap if you going to use this in your own environment or develop a similar solution using this code you need to be aware of the tile usage policy http://wiki.openstreetmap.org/wiki/Tile_usage_policy of this or any mapping service you decide to use.

To use a Mail App you need to host all the associated the files yourself somewhere see http://msdn.microsoft.com/en-us/library/office/fp161135(v=office.15).aspx for more details. With this particular application you need to host both the Mail App and also the controller service to allow you to get the attachment content.  I've put a download of the hosted code for the Mail App here https://www.dropbox.com/s/6fzzc3hpli8x5sf/HostedFiles.zip the attachment service uses a modified version of http://code.msdn.microsoft.com/office/Mail-apps-for-Office-Get-38babdc9 I've included a copy of the modified controller in the download.

eDiscovery script for reporting on large items in a Mailbox

$
0
0
When it comes to searching a Mailbox with EWS with a Script, eDiscovery on Exchange 2013 makes things a lot easier and faster by basically allowing a Mailbox (and Archive) wide search rather then a folder by folder crawl which you had to do with AQS on Exchange 2010.

I posted a paging sample a few months back to show how you can page through the results of a eDiscovery using the EWS Managed API. The following script is an application of this to produce a report of Items that are larger then a certain size in the Mailbox and Archive. To give the Folder path of the folder where the items are located the script grabs the FolderPaths and produces a report like


To run a discovery just feed it the Mailbox you want to run it against, and to set the size of the Items you want to find you can modify the following variable that holds the KQL query (the following finds items larger then 10 MB)

$KQL = "size>10485760" 

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

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4.   
  5. $KQL = "size>10485760";            
  6.   
  7. $SearchableMailboxString = $MailboxName;  
  8.   
  9. ## Load Managed API dll    
  10. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
  11.     
  12. ## Set Exchange Version    
  13. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013    
  14.     
  15. ## Create Exchange Service Object    
  16. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  17.     
  18. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  19.     
  20. #Credentials Option 1 using UPN for the windows Account    
  21. $psCred = Get-Credential    
  22. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  23. $service.Credentials = $creds        
  24.     
  25. #Credentials Option 2    
  26. #service.UseDefaultCredentials = $true    
  27.     
  28. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  29.     
  30. ## Code From http://poshcode.org/624  
  31. ## Create a compilation environment  
  32. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  33. $Compiler=$Provider.CreateCompiler()  
  34. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  35. $Params.GenerateExecutable=$False  
  36. $Params.GenerateInMemory=$True  
  37. $Params.IncludeDebugInformation=$False  
  38. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  39.   
  40. $TASource=@' 
  41.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  42.     public class TrustAll : System.Net.ICertificatePolicy { 
  43.       public TrustAll() {  
  44.       } 
  45.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  46.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  47.         System.Net.WebRequest req, int problem) { 
  48.         return true; 
  49.       } 
  50.     } 
  51.   } 
  52. '@   
  53. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  54. $TAAssembly=$TAResults.CompiledAssembly  
  55.   
  56. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  57. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  58. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  59.   
  60. ## end code from http://poshcode.org/624  
  61.     
  62. ## 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    
  63.     
  64. #CAS URL Option 1 Autodiscover    
  65. $service.AutodiscoverUrl($MailboxName,{$true})    
  66. "Using CAS Server : " + $Service.url     
  67.      
  68. #CAS URL Option 2 Hardcoded    
  69.     
  70. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  71. #$service.Url = $uri      
  72.     
  73. ## Optional section for Exchange Impersonation    
  74.     
  75. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  76.   
  77. ##get folder Paths  
  78. #Define Function to convert String to FolderPath    
  79. function ConvertToString($ipInputString){    
  80.     $Val1Text = ""    
  81.     for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){    
  82.             $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))    
  83.             $clInt++    
  84.     }    
  85.     return $Val1Text    
  86. }   
  87.   
  88.   
  89. function GetFolderPaths{  
  90.     param (  
  91.             $rootFolderId = "$( throw 'rootFolderId is a mandatory Parameter' )",  
  92.             $Archive = "$( throw 'Archive is a mandatory Parameter' )"  
  93.           )  
  94.     process{  
  95.     #Define Extended properties    
  96.     $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);    
  97.     $folderidcnt = $rootFolderId  
  98.     #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling    
  99.     $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)    
  100.     #Deep Transval will ensure all folders in the search path are returned    
  101.     $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;    
  102.     $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  103.     $PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  104.     #Add Properties to the  Property Set    
  105.     $psPropertySet.Add($PR_Folder_Path);    
  106.     $fvFolderView.PropertySet = $psPropertySet;    
  107.     #The Search filter will exclude any Search Folders    
  108.     $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")    
  109.     $fiResult = $null    
  110.     #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox    
  111.     do {    
  112.         $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)    
  113.         foreach($ffFolder in $fiResult.Folders){    
  114.             $foldpathval = $null    
  115.             #Try to get the FolderPath Value and then covert it to a usable String     
  116.             if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))    
  117.             {    
  118.                 $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)    
  119.                 $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }    
  120.                 $hexString = $hexArr -join ''    
  121.                 $hexString = $hexString.Replace("FEFF""5C00")    
  122.                 $fpath = ConvertToString($hexString)    
  123.             }    
  124.             "FolderPath : " + $fpath    
  125.             if($Archive){  
  126.                 $Script:FolderCache.Add($ffFolder.Id.UniqueId,"\Archive-Mailbox\" + $fpath); 
  127.             } 
  128.             else{ 
  129.                 $Script:FolderCache.Add($ffFolder.Id.UniqueId,$fpath); 
  130.             } 
  131.         }  
  132.         $fvFolderView.Offset += $fiResult.Folders.Count 
  133.     }while($fiResult.MoreAvailable -eq $true)   
  134.     } 
  135. } 
  136.  
  137. $Script:FolderCache = New-Object system.collections.hashtable 
  138. GetFolderPaths -rootFolderId (new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)) -Archive $false   
  139. GetFolderPaths -rootFolderId (new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveMsgFolderRoot,$MailboxName)) -Archive $true  
  140.  
  141. $gsMBResponse = $service.GetSearchableMailboxes($SearchableMailboxString, $false); 
  142. $gsMBResponse 
  143. $msbScope = New-Object  Microsoft.Exchange.WebServices.Data.MailboxSearchScope[] $gsMBResponse.SearchableMailboxes.Length 
  144. $mbCount = 0; 
  145. foreach ($sbMailbox in $gsMBResponse.SearchableMailboxes) 
  146. { 
  147.     $msbScope[$mbCount] = New-Object Microsoft.Exchange.WebServices.Data.MailboxSearchScope($sbMailbox.ReferenceId, [Microsoft.Exchange.WebServices.Data.MailboxSearchLocation]::All); 
  148.     $mbCount++; 
  149. } 
  150. $smSearchMailbox = New-Object Microsoft.Exchange.WebServices.Data.SearchMailboxesParameters 
  151. $mbq =  New-Object Microsoft.Exchange.WebServices.Data.MailboxQuery($KQL, $msbScope); 
  152. $mbqa = New-Object Microsoft.Exchange.WebServices.Data.MailboxQuery[] 1 
  153. $mbqa[0] = $mbq 
  154. $smSearchMailbox.SearchQueries = $mbqa; 
  155. $smSearchMailbox.PageSize = 100; 
  156. $smSearchMailbox.PageDirection = [Microsoft.Exchange.WebServices.Data.SearchPageDirection]::Next; 
  157. $smSearchMailbox.PerformDeduplication = $false;            
  158. $smSearchMailbox.ResultType = [Microsoft.Exchange.WebServices.Data.SearchResultType]::PreviewOnly; 
  159. $srCol = $service.SearchMailboxes($smSearchMailbox); 
  160. $rptCollection = @() 
  161.  
  162. if ($srCol[0].Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success) 
  163. { 
  164.     Write-Host ("Items Found " + $srCol[0].SearchResult.ItemCount) 
  165.     if ($srCol[0].SearchResult.ItemCount -gt 0) 
  166.     {                   
  167.         do 
  168.         { 
  169.             $smSearchMailbox.PageItemReference = $srCol[0].SearchResult.PreviewItems[$srCol[0].SearchResult.PreviewItems.Length - 1].SortValue; 
  170.             foreach ($PvItem in $srCol[0].SearchResult.PreviewItems) { 
  171.                 $rptObj = "" | select FolderPath,DateTimeReceived,Subject,Size 
  172.                 if($Script:FolderCache.ContainsKey($PvItem.ParentId.UniqueId)){ 
  173.                     $rptObj.FolderPath = $Script:FolderCache[$PvItem.ParentId.UniqueId] 
  174.                     $rptObj.DateTimeReceived = $PvItem.ReceivedTime 
  175.                     $rptObj.Subject = $PvItem.Subject 
  176.                     $rptObj.Size = $PvItem.Size 
  177.                 }else{ 
  178.                     $rptObj.DateTimeReceived = $PvItem.ReceivedTime 
  179.                     $rptObj.Subject = $PvItem.Subject 
  180.                     $rptObj.Size = $PvItem.Size 
  181.                 } 
  182.                 $rptObj 
  183.                 $rptCollection+=$rptObj 
  184.             }                         
  185.             $srCol = $service.SearchMailboxes($smSearchMailbox); 
  186.             Write-Host("Items Remaining : " + $srCol[0].SearchResult.ItemCount);  
  187.         } while ($srCol[0].SearchResult.ItemCount-gt 0 );  
  188.           
  189.     }  
  190.       
  191. }  
  192. $rptCollection | Export-Csv -NoTypeInformation -Path c:\temp\LaItemReport.csv  



EWS Contact Vcard Export script

$
0
0
A couple of months ago I posted a Vcard export script for exporting the Global Address list from Exchange 2013/ExchangeOnline using EWS's findPeople operation. To round this script out I thought I'd post another script that allows you to export from a Mailbox's Contacts Folder using EWS's built in ability to export the Contact's MimeData as a Vcard as explained in http://msdn.microsoft.com/en-us/library/office/dn672317(v=exchg.150).aspx .

This script is pretty simple it just 
  • Binds to the contacts folder of the Target mailbox
  • Enumerates the contacts in the contacts folders
  • Gets the MimeContent of each of the contacts and saves that as a vcard
To run this script just pass the primarySMTPAddress of the mailbox you want it to run against and the directory to export to eg

./exportVcardEWS.ps1 mailbox@domain.com c:\vcardexports

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

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4. $exportFolder = $args[1]  
  5.   
  6. ## Load Managed API dll    
  7. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
  8.     
  9. ## Set Exchange Version    
  10. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  11.     
  12. ## Create Exchange Service Object    
  13. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  14.     
  15. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  16.     
  17. #Credentials Option 1 using UPN for the windows Account    
  18. $psCred = Get-Credential    
  19. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  20. $service.Credentials = $creds        
  21.     
  22. #Credentials Option 2    
  23. #service.UseDefaultCredentials = $true    
  24.     
  25. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  26.     
  27. ## Code From http://poshcode.org/624  
  28. ## Create a compilation environment  
  29. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  30. $Compiler=$Provider.CreateCompiler()  
  31. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  32. $Params.GenerateExecutable=$False  
  33. $Params.GenerateInMemory=$True  
  34. $Params.IncludeDebugInformation=$False  
  35. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  36.   
  37. $TASource=@' 
  38.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  39.     public class TrustAll : System.Net.ICertificatePolicy { 
  40.       public TrustAll() {  
  41.       } 
  42.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  43.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  44.         System.Net.WebRequest req, int problem) { 
  45.         return true; 
  46.       } 
  47.     } 
  48.   } 
  49. '@   
  50. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  51. $TAAssembly=$TAResults.CompiledAssembly  
  52.   
  53. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  54. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  55. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  56.   
  57. ## end code from http://poshcode.org/624  
  58.     
  59. ## 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    
  60.     
  61. #CAS URL Option 1 Autodiscover    
  62. $service.AutodiscoverUrl($MailboxName,{$true})    
  63. "Using CAS Server : " + $Service.url     
  64.      
  65. #CAS URL Option 2 Hardcoded    
  66.     
  67. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  68. #$service.Url = $uri      
  69.     
  70. ## Optional section for Exchange Impersonation    
  71.     
  72. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  73.   
  74. # Bind to the Contacts Folder  
  75.   
  76. Function Remove-InvalidFileNameChars {  
  77.   param(  
  78.     [Parameter(Mandatory=$true,  
  79.       Position=0,  
  80.       ValueFromPipeline=$true,  
  81.       ValueFromPipelineByPropertyName=$true)]  
  82.     [String]$Name  
  83.   )  
  84.   
  85.   $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''  
  86.   $re = "[{0}]" -f [RegEx]::Escape($invalidChars)  
  87.   return ($Name -replace $re)  
  88. }  
  89.   
  90. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$MailboxName)     
  91. $Contacts = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  92.   
  93. #Define ItemView to retrive just 50 Items      
  94. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(50)      
  95. $fiItems = $null      
  96. do{      
  97.     $fiItems = $service.FindItems($Contacts.Id,$ivItemView)      
  98.     $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  99.     $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::MimeContent);  
  100.     [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
  101.     foreach($Item in $fiItems.Items){  
  102.         if($Item -is [Microsoft.Exchange.WebServices.Data.Contact]){  
  103.             $fileDisplay = $Item.Subject  
  104.             $fileDisplay = Remove-InvalidFileNameChars($fileDisplay)  
  105.             $fileName =  $exportFolder + "\" + $Item.Subject + "-" + [Guid]::NewGuid().ToString() + ".vcf" 
  106.             [System.IO.File]::WriteAllBytes($fileName,$Item.MimeContent.Content) 
  107.             "Exported " + $fileName  
  108.         }  
  109.     }      
  110.     $ivItemView.Offset += $fiItems.Items.Count      
  111. }while($fiItems.MoreAvailable -eq $true)   

Exchange 2010 GAL vcard export script

$
0
0
To round out the Vcard export scripts as many people are still running Exchange 2010 here's a Remote powershell script that will allow you to export Mailboxes (or contacts) from Active directory to Vcards and also include the GAL Photo from the AD thumbnail property if set.

 The script uses the Get-User Exchange Management Shell cmdlet to get all the Mailboxes details to include in the Vcard file and also use LDAP to read the AD thumbnailPhoto (if its been set).

before you run this script make sure you set the following variable to the directory you want the vcards exported to

$exportFolder = "c:\temp\"

You need to run this script from within the Exchange Management Shell or a Remote Powershell session

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

  1. $exportFolder = "c:\temp\" 
  2. $Mailboxes = Get-User -RecipientTypeDetails UserMailbox  
  3. foreach($Mailbox in $Mailboxes){ 
  4.     try{     
  5.          
  6.         $DisplayName = $Mailbox.DisplayName; 
  7.         write-host("Processing " + $DisplayName) 
  8.         $fileName =  $exportFolder + $DisplayName + "-" + [Guid]::NewGuid().ToString() + ".vcf" 
  9.         add-content -path $filename "BEGIN:VCARD" 
  10.         add-content -path $filename "VERSION:2.1" 
  11.         $givenName = $Mailbox.FirstName 
  12.         $surname = $Mailbox.LastName 
  13.         add-content -path $filename ("N:" + $surname + ";" + $givenName) 
  14.         add-content -path $filename ("FN:" + $Mailbox.DisplayName) 
  15.         $Department = $Mailbox.Department; 
  16.         add-content -path $filename ("EMAIL;PREF;INTERNET:" + $Mailbox.WindowsEmailAddress) 
  17.         $CompanyName = $Mailbox.Company 
  18.         add-content -path $filename ("ORG:" + $CompanyName + ";" + $Department)  
  19.         if($Mailbox.Title -ne ""){ 
  20.             add-content -path $filename ("TITLE:" + $Mailbox.Title) 
  21.         } 
  22.         $Country = "" 
  23.         $City = "" 
  24.         $Street = "" 
  25.         $State = "" 
  26.         $PCode = "" 
  27.         if($Mailbox.City -ne ""){ 
  28.             $City = $Mailbox.City 
  29.         } 
  30.         if($Mailbox.StateOrProvince -ne ""){ 
  31.             $State = $Mailbox.StateOrProvince 
  32.         } 
  33.         if($Mailbox.StreetAddress -ne ""){ 
  34.             $Street = $Mailbox.StreetAddress 
  35.         } 
  36.         if($Mailbox.CountryOrRegion -ne ""){ 
  37.             $Country = $Mailbox.CountryOrRegion 
  38.         } 
  39.         if($Mailbox.PostalCode -ne ""){ 
  40.             $PCode = $Mailbox.PostalCode 
  41.         } 
  42.         $addr =  "ADR;WORK;PREF:;" + $Country + ";" + $Street + ";" +$City + ";" + $State + ";" + $PCode + ";" + $Country 
  43.         add-content -path $filename $addr 
  44.         if($Mailbox.MobilePhone -ne ""){ 
  45.             add-content -path $filename ("TEL;CELL;VOICE:" + $Mailbox.MobilePhone)                   
  46.         } 
  47.         if($Mailbox.Phone -ne ""){ 
  48.             add-content -path $filename ("TEL;WORK;VOICE:" + $Mailbox.Phone) 
  49.         } 
  50.         if($Mailbox.Fax -ne ""){ 
  51.             add-content -path $filename ("TEL;WORK;FAX:" + $Mailbox.Fax) 
  52.         } 
  53.         if($Mailbox.HomePhone -ne ""){ 
  54.             add-content -path $filename ("TEL;HOME;VOICE:" + $Mailbox.HomePhone) 
  55.         } 
  56.         if($Mailbox.WebPage -ne ""){ 
  57.             add-content -path $filename ("URL;WORK:" + $Mailbox.WebPage) 
  58.         } 
  59.         Try{ 
  60.                 $sidbind = "LDAP://<SID=" + $Mailbox.Sid + ">" 
  61.                 $userObj = [ADSI]$sidbind 
  62.                 $photo = $userObj.thumbnailPhoto.value 
  63.                 if($photo -eq $null){"No Photo"} 
  64.                 if($photo -ne $null){ 
  65.                     add-content -path $filename "PHOTO;ENCODING=BASE64;TYPE=JPEG:" 
  66.                     $ImageString = [System.Convert]::ToBase64String($photo,[System.Base64FormattingOptions]::InsertLineBreaks) 
  67.                     add-content -path $filename $ImageString 
  68.                     add-content -path $filename "`r`n"   
  69.                 } 
  70.         } 
  71.         catch{ 
  72.          
  73.         }    
  74.         add-content -path $filename "END:VCARD" 
  75.         "Exported " + $filename   
  76.     }  
  77.     catch{  
  78.     }  
  79. }  



Creating a Mailbox Folder Growth Map with Powershell, EWS and eDiscovery

$
0
0
I've posted before on Exchange 2010 about how you can use AQS to produce a mailbox Item Age and Size reports. This same method can be used and enhanced on 2013 using eDiscovery to produce a growth report. Eg something like this that shows the mailbox growth for each folder (that has grown) based on the size of the Items in the folder for that month and an ASCII graph to show the growth vs the FolderSize


So to do this the follwing script uses eDiscovery on 2013 or Exchange Online to make a query of all the items in the primary mailbox over the period of 12 months using the following KQL query

received:2013-01-01..2014-01-01

Which queries for items that where received between these two dates, in the script I have variables to calculate this to give the exact 12 month period eg

$KQL = "received:" + $StartDate.ToString("yyyy-MM-dd") + ".." + $EndDate.ToString("yyyy-MM-dd");

The rest of the script is another variant of my eDiscovery template script with some other code to summaries the growth for each month and add the foldersizes.

To run this script you just feed in the name of the Mailbox you want to run it against eg

 .\growthMap12months.ps1 user@domain.com

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

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4. $SearchableMailboxString = $MailboxName;  
  5. $StartDate = (Get-Date).AddMonths(-11)  
  6. $EndDate = (Get-Date)  
  7.   
  8. $KQL = "received:" + $StartDate.ToString("yyyy-MM-dd") + ".." + $EndDate.ToString("yyyy-MM-dd");           
  9.  
  10.  
  11. ## Load Managed API dll    
  12. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.1\Microsoft.Exchange.WebServices.dll"    
  13.    
  14. ## Set Exchange Version    
  15. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1    
  16.    
  17. ## Create Exchange Service Object    
  18. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  19.    
  20. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  21.    
  22. #Credentials Option 1 using UPN for the windows Account    
  23. $psCred = Get-Credential    
  24. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  25. $service.Credentials = $creds        
  26.    
  27. #Credentials Option 2    
  28. #service.UseDefaultCredentials = $true    
  29.    
  30. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  31.    
  32. ## Code From http://poshcode.org/624  
  33. ## Create a compilation environment  
  34. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  35. $Compiler=$Provider.CreateCompiler()  
  36. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  37. $Params.GenerateExecutable=$False  
  38. $Params.GenerateInMemory=$True  
  39. $Params.IncludeDebugInformation=$False  
  40. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  41.   
  42. $TASource=@' 
  43.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  44.     public class TrustAll : System.Net.ICertificatePolicy { 
  45.       public TrustAll() {  
  46.       } 
  47.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  48.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  49.         System.Net.WebRequest req, int problem) { 
  50.         return true; 
  51.       } 
  52.     } 
  53.   } 
  54. '@   
  55. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  56. $TAAssembly=$TAResults.CompiledAssembly  
  57.  
  58. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  59. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  60. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  61.  
  62. ## end code from http://poshcode.org/624  
  63.    
  64. ## 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    
  65.    
  66. #CAS URL Option 1 Autodiscover    
  67. $service.AutodiscoverUrl($MailboxName,{$true})    
  68. "Using CAS Server : " + $Service.url     
  69.     
  70. #CAS URL Option 2 Hardcoded    
  71.    
  72. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  73. #$service.Url = $uri      
  74.    
  75. ## Optional section for Exchange Impersonation    
  76.    
  77. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  78. function ConvertToString($ipInputString){    
  79.     $Val1Text = ""    
  80.     for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){    
  81.             $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))    
  82.             $clInt++    
  83.     }    
  84.     return $Val1Text    
  85. }   
  86.   
  87.   
  88. function GetFolderPaths{  
  89.     param (  
  90.             $rootFolderId = "$( throw 'rootFolderId is a mandatory Parameter' )",  
  91.             $Archive = "$( throw 'Archive is a mandatory Parameter' )"  
  92.           )  
  93.     process{  
  94.     #Define Extended properties    
  95.     $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);    
  96.     $PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);  
  97.     $folderidcnt = $rootFolderId  
  98.     #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling    
  99.     $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)    
  100.     #Deep Transval will ensure all folders in the search path are returned    
  101.     $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;    
  102.     $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  103.     $PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  104.     #Add Properties to the  Property Set    
  105.     $psPropertySet.Add($PR_Folder_Path);    
  106.     $psPropertySet.Add($PR_MESSAGE_SIZE_EXTENDED)  
  107.     $fvFolderView.PropertySet = $psPropertySet;    
  108.     #The Search filter will exclude any Search Folders    
  109.     $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")    
  110.     $fiResult = $null    
  111.     #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox    
  112.     do {    
  113.         $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)    
  114.         foreach($ffFolder in $fiResult.Folders){  
  115.             $rptFld = "" | Select FolderPath,TotalItemCount,TotalFolderSize,PeriodGrowthSize  
  116.             $valDate = $StartDate  
  117.             do{  
  118.                 $MonthVal = $valDate.ToString("MMMM") + $valDate.ToString("yyyy")  
  119.                 Add-Member -InputObject $rptFld -MemberType NoteProperty -Name $MonthVal -Value 0   
  120.                 $valDate = $valDate.AddMonths(1)  
  121.             }while($valDate -le $EndDate)  
  122.             Add-Member -InputObject $rptFld -MemberType NoteProperty -Name "GrowthGraph" -Value ""  
  123.             $foldpathval = $null    
  124.             #Try to get the FolderPath Value and then covert it to a usable String     
  125.             if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))    
  126.             {    
  127.                 $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)    
  128.                 $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }    
  129.                 $hexString = $hexArr -join ''    
  130.                 $hexString = $hexString.Replace("FEFF""5C00")    
  131.                 $fpath = ConvertToString($hexString)    
  132.             }   
  133.             $folderSize = $null  
  134.             if ($ffFolder.TryGetProperty($PR_MESSAGE_SIZE_EXTENDED,[ref] $folderSize))    
  135.             {     
  136.                 if ($folderSize -gt 0){  
  137.                     $rptFld.TotalFolderSize = $folderSize  
  138.                 }  
  139.                 else{  
  140.                     $rptFld.TotalFolderSize = $folderSize  
  141.                 }  
  142.             }  
  143.             "FolderPath : " + $fpath  
  144.             if($Archive){  
  145.                 $fpath = "\Archive-Mailbox\" + $fpath; 
  146.             } 
  147.             $rptFld.FolderPath = $fpath 
  148.             $rptFld.TotalItemCount = $ffFolder.TotalCount 
  149.             $Script:FolderCache.Add($ffFolder.Id.UniqueId,$rptFld); 
  150.         }  
  151.         $fvFolderView.Offset += $fiResult.Folders.Count 
  152.     }while($fiResult.MoreAvailable -eq $true)   
  153.     } 
  154. } 
  155.  
  156. $Script:FolderCache = New-Object system.collections.hashtable 
  157. GetFolderPaths -rootFolderId (new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)) -Archive $false   
  158. GetFolderPaths -rootFolderId (new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsRoot,$MailboxName)) -Archive $false   
  159.  
  160. $gsMBResponse = $service.GetSearchableMailboxes($SearchableMailboxString, $false); 
  161. $gsMBResponse 
  162. $msbScope = New-Object  Microsoft.Exchange.WebServices.Data.MailboxSearchScope[] $gsMBResponse.SearchableMailboxes.Length 
  163. $mbCount = 0; 
  164. foreach ($sbMailbox in $gsMBResponse.SearchableMailboxes) 
  165. { 
  166.     $msbScope[$mbCount] = New-Object Microsoft.Exchange.WebServices.Data.MailboxSearchScope($sbMailbox.ReferenceId, [Microsoft.Exchange.WebServices.Data.MailboxSearchLocation]::PrimaryOnly); 
  167.     $mbCount++; 
  168. } 
  169. $smSearchMailbox = New-Object Microsoft.Exchange.WebServices.Data.SearchMailboxesParameters 
  170. $mbq =  New-Object Microsoft.Exchange.WebServices.Data.MailboxQuery($KQL, $msbScope); 
  171. $mbqa = New-Object Microsoft.Exchange.WebServices.Data.MailboxQuery[] 1 
  172. $mbqa[0] = $mbq 
  173. $smSearchMailbox.SearchQueries = $mbqa; 
  174. $smSearchMailbox.PageSize = 100; 
  175. $smSearchMailbox.PageDirection = [Microsoft.Exchange.WebServices.Data.SearchPageDirection]::Next; 
  176. $smSearchMailbox.PerformDeduplication = $false;            
  177. $smSearchMailbox.ResultType = [Microsoft.Exchange.WebServices.Data.SearchResultType]::PreviewOnly; 
  178. $srCol = $service.SearchMailboxes($smSearchMailbox); 
  179. $rptCollection = @{} 
  180.  
  181. if ($srCol[0].Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success) 
  182. { 
  183.     Write-Host ("Items Found " + $srCol[0].SearchResult.ItemCount) 
  184.     if ($srCol[0].SearchResult.ItemCount -gt 0) 
  185.     {                   
  186.         do 
  187.         { 
  188.             $smSearchMailbox.PageItemReference = $srCol[0].SearchResult.PreviewItems[$srCol[0].SearchResult.PreviewItems.Length - 1].SortValue; 
  189.             foreach ($PvItem in $srCol[0].SearchResult.PreviewItems) { 
  190.                 $rptObj = "" | select FolderPath,DateTimeReceived,Subject,Size 
  191.                 if($Script:FolderCache.ContainsKey($PvItem.ParentId.UniqueId)){ 
  192.                     if($rptCollection.ContainsKey($PvItem.ParentId.UniqueId) -eq $false){ 
  193.                         $rptCollection.Add($PvItem.ParentId.UniqueId,$Script:FolderCache[$PvItem.ParentId.UniqueId]) 
  194.                     } 
  195.                     $MonthVal = $PvItem.ReceivedTime.ToString("MMMM") + $PvItem.ReceivedTime.ToString("yyyy") 
  196.                     $rptCollection[$PvItem.ParentId.UniqueId]."$MonthVal" += $PvItem.Size 
  197.                 } 
  198.                          
  199.             }                         
  200.             $srCol = $service.SearchMailboxes($smSearchMailbox); 
  201.             Write-Host("Items Remaining : " + $srCol[0].SearchResult.ItemCount); 
  202.         } while ($srCol[0].SearchResult.ItemCount-gt 0 ); 
  203.          
  204.     } 
  205.      
  206. } 
  207. $rptOutput = @() 
  208. $rptCollection.GetEnumerator() |  ForEach-Object{ 
  209.     $valDate = $StartDate 
  210.         do{ 
  211.             $MonthVal = $valDate.ToString("MMMM") + $valDate.ToString("yyyy") 
  212.             $_.Value.PeriodGrowthSize += $_.Value."$MonthVal" 
  213.             $_.Value."$MonthVal" = [math]::round($_.Value."$MonthVal"/1Mb,2) 
  214.             $valDate = $valDate.AddMonths(1) 
  215.         }while($valDate -le $EndDate) 
  216.         $GrowthPercent = [Math]::round((($_.Value.PeriodGrowthSize/$_.Value.TotalFolderSize) * 100)) 
  217.         $_.Value.PeriodGrowthSize = [math]::round($_.Value.PeriodGrowthSize/1Mb,2) 
  218.         $_.Value.TotalFolderSize = [math]::round($_.Value.TotalFolderSize /1Mb, 2) 
  219.         
  220.     $GrowthPercent 
  221.     $PercentGraph = ""   
  222.     for($intval=0;$intval -lt 100;$intval+=4){   
  223.         if($GrowthPercent -gt $intval){   
  224.             $PercentGraph += ""   
  225.         }   
  226.         else{          
  227.             $PercentGraph += "░"    
  228.         }    
  229.     }    
  230.     $_.Value.GrowthGraph = $PercentGraph      
  231.     $rptOutput += $_.Value  
  232. }  
  233. $rptOutput | Export-Csv -NoTypeInformation -Path c:\temp\$MailboxName-mbgrowMap.csv -Encoding UTF8  
  234. $rptOutput | Select FolderPath,GrowthGraph  


Reporting on expired recurring meetings with EWS and Powershell

$
0
0
Someone asked an interesting question about recurring Appointments in Exchange recently that I thought I'd expand on a bit with blog post. Recurrence in Appointments,Meeting and Tasks are one of the more useful features of Exchange, but they are also one the features that are harder for developers and anybody writing code or scripts to get a handle on. These days there is some really good technical documentation on this so I won't explain too much but firstly http://msdn.microsoft.com/EN-US/library/office/dn727655(v=exchg.150).aspx but also the Exchange Protocol Document http://msdn.microsoft.com/en-us/library/ee201188(v=exchg.80).aspx are a must to read.

To refine this down a bit with Calendar Appointments if you have a recurring Appointment you don't have separate objects for each occurrence of the recurring Appointment, the recurrence is just a pattern stored in a property on what is called the master instance of that particular Appointment, Meeting or Task. Exception information is stored as part of the Recurrence pattern (while the actual data from the Exception if there is anything different from the Master instance eg say you add an attachment to an exception this data gets stored as AttachedItem on the Master instance).

What this all means is that when it comes time to actually query the Calendar Appointments these recurrence patterns need to be expanded. To do this in EWS you use the CalendarView class and you specify a Start and End Date for your query. Exchange will then do all the hard work for you and return the expanded Appointments (and exceptions) to you.

To get back to the actual question however of how you would find expired recurring Calendar Appointments you have to forget about making an expansion query and instead you want to make a query to return a list of these master instances of recurring calendar Appointments and then filter these at the client side to work out what has expired (or if you want to do other reporting like seeing who has made non expiring appointments you can do this).

So to do this you can use the FindItem operation with a restriction on the PidLidRecurring property http://msdn.microsoft.com/en-us/library/ee157994(v=EXCHG.80).aspx which looks like this

  1. $Recurring = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Appointment, 0x8223,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Boolean);   
  2.   
  3. $sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($Recurring,$true)   

This will then return a list of Master Appointment instances, to get the full details of the Recurrence of each appointment you need to do a GetItem on each of the master instance which in the Managed API you can use the LoadPropertiesForItems method for. This will make a batch GetItem request to the Exchange Server to return the detail on all Items your request it for.

Once you have the recurrence information you can then filter those Meetings that have an End Date (or Recurrence number) set by using the HasEnd property.  Then to check the Last Occurrence you can use the LastOccurrence property. If you want to report on other things like meetings that don't have an EndDate or meetings that are expiring in the next week or month this is where you can put your own customization.

The following script will produce a report of any expired Meetings in a Mailbox's calendar and save this to a CSV file. I've put a download of this script here the code itself looks like

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4.   
  5. ## Load Managed API dll    
  6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.1\Microsoft.Exchange.WebServices.dll"    
  7.     
  8. ## Set Exchange Version    
  9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  10.     
  11. ## Create Exchange Service Object    
  12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  13.     
  14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  15.     
  16. #Credentials Option 1 using UPN for the windows Account    
  17. $psCred = Get-Credential    
  18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  19. $service.Credentials = $creds        
  20.     
  21. #Credentials Option 2    
  22. #service.UseDefaultCredentials = $true    
  23.     
  24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  25.     
  26. ## Code From http://poshcode.org/624  
  27. ## Create a compilation environment  
  28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  29. $Compiler=$Provider.CreateCompiler()  
  30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  31. $Params.GenerateExecutable=$False  
  32. $Params.GenerateInMemory=$True  
  33. $Params.IncludeDebugInformation=$False  
  34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  35.   
  36. $TASource=@' 
  37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  38.     public class TrustAll : System.Net.ICertificatePolicy { 
  39.       public TrustAll() {  
  40.       } 
  41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  43.         System.Net.WebRequest req, int problem) { 
  44.         return true; 
  45.       } 
  46.     } 
  47.   } 
  48. '@   
  49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  50. $TAAssembly=$TAResults.CompiledAssembly  
  51.   
  52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  55.   
  56. ## end code from http://poshcode.org/624  
  57.     
  58. ## 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    
  59.     
  60. #CAS URL Option 1 Autodiscover    
  61. $service.AutodiscoverUrl($MailboxName,{$true})    
  62. "Using CAS Server : " + $Service.url     
  63.      
  64. #CAS URL Option 2 Hardcoded    
  65.     
  66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  67. #$service.Url = $uri      
  68.     
  69. ## Optional section for Exchange Impersonation    
  70.     
  71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  72.   
  73. # Bind to the Calendar Folder  
  74. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)     
  75. $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  76.   
  77. $Recurring = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Appointment, 0x8223,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Boolean);   
  78.   
  79. $sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($Recurring,$true)   
  80.   
  81.   
  82. #Define ItemView to retrive just 1000 Items      
  83. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)     
  84. $rptCollection = @()  
  85. $fiItems = $null      
  86. do{   
  87.     $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  88.     $fiItems = $service.FindItems($Calendar.Id,$sfItemSearchFilter,$ivItemView)     
  89.     if($fiItems.Items.Count -gt 0){  
  90.         [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
  91.         foreach($Item in $fiItems.Items){   
  92.             if($Item.Recurrence.HasEnd){  
  93.                 if($Item.LastOccurrence.End -lt (Get-Date)){  
  94.                     $rptObj = "" | Select Mailbox,Organizer,AppointmentSubject,FirstOccuranceStart,FirstOccuranceEnd,LastOccuranceStart,LastOccuranceEnd  
  95.                     Write-Host "Appointment : "  $Item.Subject  
  96.                     Write-Host "Last Occurance : " $Item.LastOccurrence.Start  
  97.                     $rptObj.Mailbox = $MailboxName  
  98.                     $rptObj.Organizer = $Item.Organizer.Name  
  99.                     $rptObj.AppointmentSubject = $Item.Subject  
  100.                     $rptObj.FirstOccuranceStart = $Item.FirstOccurrence.Start  
  101.                     $rptObj.FirstOccuranceEnd = $Item.FirstOccurrence.End  
  102.                     $rptObj.LastOccuranceStart = $Item.LastOccurrence.Start  
  103.                     $rptObj.LastOccuranceEnd = $Item.LastOccurrence.End               
  104.                     $rptCollection += $rptObj  
  105.                 }  
  106.             }  
  107.         }  
  108.     }  
  109.     $ivItemView.Offset += $fiItems.Items.Count      
  110. }while($fiItems.MoreAvailable -eq $true)   
  111. $rptCollection | Export-Csv -NoTypeInformation -Path c:\Temp\$MailboxName-ExpiredRecurringMeetings.csv  
  112. Write-Host "Report written to " c:\Temp\$MailboxName-ExpiredRecurringMeetings.csv  



Getting Folder Sizes and other Stats via EWS with Powershell

$
0
0
Somebody asked a question the other week about getting all the Folder Sizes via EWS which you can do easily using the PR_MESSAGE_SIZE_EXTENDED property and FindFolders operation (you can also get the folder size's using the Exchange Management Shell via Get-MailboxFolderStatistics cmdlet). But there is some other interesting stuff you can get via EWS that you can't from the EMS cmdlet such as the Default FolderClass (eg the PR_CONTAINER_CLASS_W http://msdn.microsoft.com/en-us/library/office/cc839839(v=office.15).aspx) which will tell you what items are being stored in that particular Folder (although as documented it's not a mandatory property although its absence in the past has caused problem in OWA etc). Another property that looks interesting but doesn't seem to be well documented is the PR_ATTACH_ON_NORMAL_MSG_COUNT which appears to the be the total number of attachments on messages in that folder including seemly inline attachment (Note I can't confirm this as it doesn't appear to be documented and can only give anecdotal test results so do your own testing if your interested in this).

So with all these interesting properties you can put together a different type of Mailbox statistics script that will grab Folder stats by FolderClass and show the following info about a mailbox


I've put a download of this script here to run this script enter the EmailAddress of the mailbox you want to run it against and It will will output a CSV to the c:\temp directory the code itself looks like

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4.   
  5. ## Load Managed API dll    
  6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.1\Microsoft.Exchange.WebServices.dll"    
  7.     
  8. ## Set Exchange Version    
  9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1    
  10.     
  11. ## Create Exchange Service Object    
  12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  13.     
  14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  15.     
  16. #Credentials Option 1 using UPN for the windows Account    
  17. $psCred = Get-Credential    
  18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  19. $service.Credentials = $creds        
  20.     
  21. #Credentials Option 2    
  22. #service.UseDefaultCredentials = $true    
  23.     
  24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  25.     
  26. ## Code From http://poshcode.org/624  
  27. ## Create a compilation environment  
  28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  29. $Compiler=$Provider.CreateCompiler()  
  30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  31. $Params.GenerateExecutable=$False  
  32. $Params.GenerateInMemory=$True  
  33. $Params.IncludeDebugInformation=$False  
  34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  35.   
  36. $TASource=@' 
  37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  38.     public class TrustAll : System.Net.ICertificatePolicy { 
  39.       public TrustAll() {  
  40.       } 
  41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  43.         System.Net.WebRequest req, int problem) { 
  44.         return true; 
  45.       } 
  46.     } 
  47.   } 
  48. '@   
  49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  50. $TAAssembly=$TAResults.CompiledAssembly  
  51.   
  52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  55.   
  56. ## end code from http://poshcode.org/624  
  57.     
  58. ## 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    
  59.     
  60. #CAS URL Option 1 Autodiscover    
  61. $service.AutodiscoverUrl($MailboxName,{$true})    
  62. "Using CAS Server : " + $Service.url     
  63.      
  64. #CAS URL Option 2 Hardcoded    
  65.     
  66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  67. #$service.Url = $uri      
  68.     
  69. ## Optional section for Exchange Impersonation    
  70.     
  71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  72. function ConvertToString($ipInputString){    
  73.     $Val1Text = ""    
  74.     for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){    
  75.             $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))    
  76.             $clInt++    
  77.     }    
  78.     return $Val1Text    
  79. }   
  80.   
  81. $FolderClassrpt = @{}  
  82. function GetFolderSizes{  
  83.     param (  
  84.             $rootFolderId = "$( throw 'rootFolderId is a mandatory Parameter' )"  
  85.           )  
  86.     process{  
  87.     #Define Extended properties    
  88.     $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);    
  89.     $PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);  
  90.     $folderidcnt = $rootFolderId  
  91.     #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling    
  92.     $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)    
  93.     #Deep Transval will ensure all folders in the search path are returned    
  94.     $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;    
  95.     $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  96.     $PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  97.     $PR_ATTACH_ON_NORMAL_MSG_COUNT = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x66B1, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);  
  98.     #Add Properties to the  Property Set    
  99.     $psPropertySet.Add($PR_Folder_Path);    
  100.     $psPropertySet.Add($PR_MESSAGE_SIZE_EXTENDED)  
  101.     $psPropertySet.Add($PR_ATTACH_ON_NORMAL_MSG_COUNT)  
  102.     $fvFolderView.PropertySet = $psPropertySet;    
  103.     #The Search filter will exclude any Search Folders    
  104.     $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")    
  105.     $fiResult = $null    
  106.     #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox    
  107.     do {    
  108.         $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)    
  109.         foreach($ffFolder in $fiResult.Folders){  
  110.             $foldpathval = $null    
  111.             #Try to get the FolderPath Value and then covert it to a usable String     
  112.             if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))    
  113.             {    
  114.                 $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)    
  115.                 $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }    
  116.                 $hexString = $hexArr -join ''    
  117.                 $hexString = $hexString.Replace("FEFF""5C00")    
  118.                 $fpath = ConvertToString($hexString)    
  119.             }   
  120.             $folderSize = $null  
  121.             [Void]$ffFolder.TryGetProperty($PR_MESSAGE_SIZE_EXTENDED,[ref] $folderSize)  
  122.             [Int64]$attachcnt = 0  
  123.             [Void]$ffFolder.TryGetProperty($PR_ATTACH_ON_NORMAL_MSG_COUNT,[ref] $attachcnt)  
  124.             if($attachcnt -eq $null){  
  125.                 $attachcnt = 0  
  126.             }  
  127.             "FolderPath : " + $fpath + " : " + $folderSize  
  128.             $fldClass = $ffFolder.FolderClass  
  129.             if($fldClass -eq $null){$fldClass = "IPF.Note"}  
  130.             if($FolderClassrpt.ContainsKey($fldClass)){  
  131.                 $FolderClassrpt[$fldClass].NumberOfFolders += 1  
  132.                 $FolderClassrpt[$fldClass].AttachOnMsgCount += $attachcnt  
  133.                 $FolderClassrpt[$fldClass].ItemSize += [Int64]$folderSize  
  134.                 $FolderClassrpt[$fldClass].ItemCount += [Int64]$ffFolder.TotalCount  
  135.             }  
  136.             else{  
  137.                 $rptObj = "" | select FolderClass,NumberOfFolders,AttachOnMsgCount,ItemSize,ItemCount  
  138.                 $rptObj.FolderClass = $fldClass  
  139.                 $FolderClassrpt[$fldClass].NumberOfFolders  
  140.                 $rptObj.ItemSize = [Int64]$folderSize  
  141.                 $rptObj.ItemCount = [Int64]$ffFolder.TotalCount  
  142.                 $rptObj.AttachOnMsgCount += $attachcnt  
  143.                 $rptObj.NumberOfFolders = 1  
  144.                 $FolderClassrpt.Add($fldClass,$rptObj)  
  145.             }  
  146.         }   
  147.         $fvFolderView.Offset += $fiResult.Folders.Count  
  148.     }while($fiResult.MoreAvailable -eq $true)    
  149.     }  
  150. }  
  151. GetFolderSizes -rootFolderId (new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName))     
  152.   
  153. $FolderClassrpt.Values | select FolderClass,NumberOfFolders,AttachOnMsgCount,ItemCount,@{label="TotalSize(MB)";expression={[math]::Round($_.ItemSize/1MB,2)}}  | export-csv c:\temp\$MailboxName-fldsizebyclass.csv -NoTypeInformation   


Sent and Received Time on a Message and EWS

$
0
0
This one has come up a couple of times for me over the last couple of weeks so I thought I'd put together a post to expand on the Subject a little.

The easiest place to start before talking about Exchange is to look at a ordinary MIME message and its associated headers. In a MIME message there is one Message date  header http://tools.ietf.org/html/rfc4021#section-2.1.1 which outlines "Specifies the date and time at which the creator of the message indicated that the message was complete and ready to enter the mail delivery system" which is a way of saying that its the Sent Time. eg In a Message

Subject: Re: Oh no
Date: Thu, 14 Aug 2014 18:44:52 +1000
Message-ID: <nk0h69wn6waj8s32rnc3kma0.1408005882691@email.android.com>

 As the Message traverses various MTA's along the way to its final destinations, Received headers with dates are added to the Transport Headers  of a message indicating the date\time a particular MTA's processed the message eg

Received: from BY2PR03MB459.namprd03.prod.outlook.com (10.141.141.147) by
 DM2PR03MB463.namprd03.prod.outlook.com (10.141.85.20) with Microsoft SMTP
 Server (TLS) id 15.0.859.15 via Mailbox Transport; Wed, 29 Jan 2014 22:06:05
 +0000

When the Message finally arrives at is destination and is delivered by the Store to a users Mailbox two MAPI properties will be created to  reflect the Sent Time and also the Date Time the message was delivered by the store (which should match (most recent) received header in the message). With POP3 and and some POP downloaders this is where the date can get a little offset and not represent the real time of message delivery. But sticking to Exchange the following two props get set

PR_MESSAGE_DELIVERY_TIME which in EWS is also represented by the Strongly typed DateTimeReceived property

PR_CLIENT_SUBMIT_TIME property which in EWS is represented by the Strongly typed DateTimeSent property.

Exchange when it stores these dates like with other dates will store these in UTC format. When your using Outlook with the default views it will use the PR_MESSAGE_DELIVERY_TIME for every Mail folder other then the Sent Items where the view will use the PR_CLIENT_SUBMIT_TIME.

So whenever you importing EML's and you see an unexpected Received (or Sent) by date the first thing to check is the Transport Headers and look at the most recent received header. If your missing these headers then that maybe why your dates aren't what you expect.

In EWS you can access the Transport Headers via the InternetMessageHeaders strongly typed property or you can use the PR_TRANSPORT_MESSAGE_HEADERS extended properties. Depending on the version of Exchange you are using there can be issues with the strongly typed property so you should read http://msdn.microsoft.com/EN-US/library/office/hh545614(v=exchg.140).aspx . Because of the size of these properties this information will only be returned when you use a GetItem's operation

The other thing you can do with these message dates is track the amount of time it took from submit to the delivery of a message eg the following script will use EWS to grab both the PR_MESSAGE_DELIVERY_TIME  and PR_CLIENT_SUBMIT_TIME MAPI properties and use those to calculate the delivery time is also parses the Sent Header datetime and creates a CSV of the output. I've put a download of this script here the code looks like

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4.   
  5. ## Load Managed API dll    
  6.   
  7. ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT  
  8. $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")  
  9. if (Test-Path $EWSDLL)  
  10.     {  
  11.     Import-Module $EWSDLL  
  12.     }  
  13. else  
  14.     {  
  15.     "$(get-date -format yyyyMMddHHmmss):"  
  16.     "This script requires the EWS Managed API 1.2 or later."  
  17.     "Please download and install the current version of the EWS Managed API from"  
  18.     "http://go.microsoft.com/fwlink/?LinkId=255472"  
  19.     ""  
  20.     "Exiting Script."  
  21.     exit  
  22.     }  
  23.     
  24. ## Set Exchange Version    
  25. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  26.     
  27. ## Create Exchange Service Object    
  28. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  29.     
  30. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  31.     
  32. #Credentials Option 1 using UPN for the windows Account    
  33. $psCred = Get-Credential    
  34. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  35. $service.Credentials = $creds        
  36.     
  37. #Credentials Option 2    
  38. #service.UseDefaultCredentials = $true    
  39.     
  40. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  41.     
  42. ## Code From http://poshcode.org/624  
  43. ## Create a compilation environment  
  44. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  45. $Compiler=$Provider.CreateCompiler()  
  46. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  47. $Params.GenerateExecutable=$False  
  48. $Params.GenerateInMemory=$True  
  49. $Params.IncludeDebugInformation=$False  
  50. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  51.   
  52. $TASource=@' 
  53.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  54.     public class TrustAll : System.Net.ICertificatePolicy { 
  55.       public TrustAll() {  
  56.       } 
  57.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  58.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  59.         System.Net.WebRequest req, int problem) { 
  60.         return true; 
  61.       } 
  62.     } 
  63.   } 
  64. '@   
  65. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  66. $TAAssembly=$TAResults.CompiledAssembly  
  67.   
  68. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  69. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  70. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  71.   
  72. ## end code from http://poshcode.org/624  
  73.     
  74. ## 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    
  75.     
  76. #CAS URL Option 1 Autodiscover    
  77. $service.AutodiscoverUrl($MailboxName,{$true})    
  78. "Using CAS Server : " + $Service.url     
  79.      
  80. #CAS URL Option 2 Hardcoded    
  81.     
  82. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  83. #$service.Url = $uri      
  84.     
  85. ## Optional section for Exchange Impersonation    
  86.     
  87. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  88.   
  89. # Bind to the Inbox Folder  
  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)  
  92.   
  93. $PR_MESSAGE_DELIVERY_TIME = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E06, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime)  
  94. $PR_CLIENT_SUBMIT_TIME = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0039, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime)  
  95. $PR_TRANSPORT_MESSAGE_HEADERS = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x007D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);  
  96. $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly)    
  97. $psPropset.Add($PR_CLIENT_SUBMIT_TIME)  
  98. $psPropset.Add($PR_MESSAGE_DELIVERY_TIME)  
  99. $psPropset.Add($PR_TRANSPORT_MESSAGE_HEADERS)  
  100. $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject)  
  101. #Define ItemView to retrive just 1000 Items      
  102. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)    
  103. $ivItemView.PropertySet = $psPropset  
  104. $rptCollection = @()  
  105. $fiItems = $null      
  106. do{      
  107.     $fiItems = $service.FindItems($Inbox.Id,$ivItemView)      
  108.     [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
  109.     foreach($Item in $fiItems.Items){      
  110.         $Headers = $null;  
  111.         $ClientSubmitTime = $null  
  112.         $DeliveryTime = $null  
  113.         [Void]$Item.TryGetProperty($PR_CLIENT_SUBMIT_TIME,[ref]$ClientSubmitTime)  
  114.         [Void]$Item.TryGetProperty($PR_MESSAGE_DELIVERY_TIME,[ref]$DeliveryTime)  
  115.         if($Item.TryGetProperty($PR_TRANSPORT_MESSAGE_HEADERS,[ref]$Headers)){  
  116.             $slen = $Headers.ToLower().IndexOf("`ndate: ")  
  117.             if($slen -gt 0){  
  118.                 $elen = $Headers.IndexOf("`r`n",$slen)  
  119.                 $TimeSpan =  NEW-TIMESPAN –Start $ClientSubmitTime –End $DeliveryTime   
  120.                 $rptobj = "" | select Subject,HeaderDate,DELIVERY_TIME,SUBMIT_TIME,Diff  
  121.                 $rptobj.Subject = $Item.Subject  
  122.                 $parsedDate = $Headers.Substring(($slen+7),($elen-($slen+7)))                 
  123.                 $rptobj.HeaderDate = [DateTime]::Parse($parsedDate).ToLocalTime()  
  124.                 $rptobj.DELIVERY_TIME = $DeliveryTime.ToLocalTime()  
  125.                 $rptobj.SUBMIT_TIME = $ClientSubmitTime.ToLocalTime()  
  126.                 $rptobj.Diff = [Math]::Round($TimeSpan.TotalMinutes,0)  
  127.                 $rptCollection += $rptobj  
  128.             }  
  129.         }        
  130.     }      
  131.     $ivItemView.Offset += $fiItems.Items.Count  
  132.     Write-Host ("Processed " + $ivItemView.Offset + " of " + $fiItems.TotalCount)  
  133. }while($fiItems.MoreAvailable -eq $true)   
  134. $rptCollection | Export-Csv -NoTypeInformation -Path "c:\Temp\$mailboxName-mTimes.csv"  
  135. Write-Host("Exported to c:\Temp\$mailboxName-mTimes.csv")  

Creating a Mailbox alias usage report with EWS and Powershell

$
0
0
Before starting this one I want to point out the method I'm going to demonstrate in this post is not the best method to use to do this. If you have Message Tracking available then you can do this much more accurately using the Message tracking logs.  But if you don't have this available to you or you just want to see what you can do with EWS then this is the post for you.

Many people have one or more email addresses assigned to their mailboxes (they get referred to using different names alias's, proxyaddress's, secondary address's, alternate address's etc) but in Outlook and OWA it can be hard to see what email address has been used to send you a mail because of the delivery process and the way the addresses are resolved. To get the actual email address a particular email was sent to one workaround you can use in EWS is to use the PidTagTransportMessageHeaders property and then parse the TO, CC and BCC Headers (note generally the BCC header won't be there even if you have been BCC'd) from the transport headers.

So what this script does is first it uses the Exchange Management Shell's get-mailbox cmdlet to grab all proxyaddresses for the target account. This is needed because there is noway in EWS to get all the Mailbox proxy addresses (you can use ResolveName to get the Mailbox Contact from AD but you will only get returned the first 3 addresses for the mailbox). It stores these addresses in a hashtable that can then be used in to do lookups so when processing all the emailaddress in header we can filter to only build a report of the current mailboxes addresses being used. The next part of the code is some normal EWS fair to grab all the messages in the Inbox for the last 7 days and then grab the PidTagTransportMessageHeaders  property and parse the TO,CC and BCC addresses from the headers using a linear parser and some RegEX (I know some people don't like linear parsing but I always find they work well).  The script then builds two reports the first report is a summary report showing each of the alias's that where used and how many TO,CC and BCC where used eg



The other report that the script builds is an Item Report which shows for each Item in the Inbox which method was used eg


Now as I mentioned there are a few problems with the method used in the script if you have been BCC'd on a message generally there won't be any information in the headers to reflect which email address was used. Also if the message was sent to a Distribution list or it's a piece of GrayMail depending on whatever method the mailer has used there maybe no header. So in this case it will show other in the method and the last parsed TO address.

To run this script you need to have a Remote Powershell session open so you can run Get-Mailbox and then just run the script with the PrimaryEmaillAddress of the mailbox you want to run the script against as a parameter. The script will output the two reports to the temp directory

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

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4.   
  5. $Mailbox = get-mailbox $MailboxName  
  6. $mbAddress = @{}  
  7. foreach($emEmail in $Mailbox.EmailAddresses){  
  8.     if($emEmail.Tolower().Contains("smtp:")){  
  9.         $mbAddress.Add($emEmail.SubString(5).Tolower(),0)  
  10.     }  
  11. }  
  12.   
  13. ## Load Managed API dll    
  14.   
  15. ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT  
  16. $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")  
  17. if (Test-Path $EWSDLL)  
  18.     {  
  19.     Import-Module $EWSDLL  
  20.     }  
  21. else  
  22.     {  
  23.     "$(get-date -format yyyyMMddHHmmss):"  
  24.     "This script requires the EWS Managed API 1.2 or later."  
  25.     "Please download and install the current version of the EWS Managed API from"  
  26.     "http://go.microsoft.com/fwlink/?LinkId=255472"  
  27.     ""  
  28.     "Exiting Script."  
  29.     exit  
  30.     }  
  31.     
  32. ## Set Exchange Version    
  33. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  34.     
  35. ## Create Exchange Service Object    
  36. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  37.     
  38. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  39.     
  40. #Credentials Option 1 using UPN for the windows Account    
  41. $psCred = Get-Credential    
  42. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  43. $service.Credentials = $creds        
  44.     
  45. #Credentials Option 2    
  46. #service.UseDefaultCredentials = $true    
  47.     
  48. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  49.     
  50. ## Code From http://poshcode.org/624  
  51. ## Create a compilation environment  
  52. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  53. $Compiler=$Provider.CreateCompiler()  
  54. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  55. $Params.GenerateExecutable=$False  
  56. $Params.GenerateInMemory=$True  
  57. $Params.IncludeDebugInformation=$False  
  58. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  59.   
  60. $TASource=@' 
  61.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  62.     public class TrustAll : System.Net.ICertificatePolicy { 
  63.       public TrustAll() {  
  64.       } 
  65.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  66.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  67.         System.Net.WebRequest req, int problem) { 
  68.         return true; 
  69.       } 
  70.     } 
  71.   } 
  72. '@   
  73. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  74. $TAAssembly=$TAResults.CompiledAssembly  
  75.   
  76. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  77. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  78. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  79.   
  80. ## end code from http://poshcode.org/624  
  81.     
  82. ## 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    
  83.     
  84. #CAS URL Option 1 Autodiscover    
  85. $service.AutodiscoverUrl($MailboxName,{$true})    
  86. "Using CAS Server : " + $Service.url     
  87.      
  88. #CAS URL Option 2 Hardcoded    
  89.     
  90. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  91. #$service.Url = $uri      
  92.     
  93. ## Optional section for Exchange Impersonation    
  94.     
  95. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  96.   
  97. # Bind to the Inbox Folder  
  98. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  99. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  100. $PR_TRANSPORT_MESSAGE_HEADERS = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x007D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);  
  101. $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly)    
  102. $psPropset.Add($PR_TRANSPORT_MESSAGE_HEADERS)  
  103. $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject)  
  104. $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size)  
  105. $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived)  
  106. #Define ItemView to retrive just 1000 Items      
  107. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)    
  108. $ivItemView.PropertySet = $psPropset  
  109. $rptCollection = @()  
  110. $rptcntCount = @{}  
  111. #Find Items that are unread  
  112. $sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, (Get-Date).AddDays(-7));   
  113. $fiItems = $null      
  114. do{      
  115.     $fiItems = $service.FindItems($Inbox.Id,$sfItemSearchFilter,$ivItemView)      
  116.     [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
  117.     foreach($Item in $fiItems.Items){      
  118.         $Headers = $null;  
  119.         $Address = "";  
  120.         $Method = ""  
  121.         if($Item.TryGetProperty($PR_TRANSPORT_MESSAGE_HEADERS,[ref]$Headers)){  
  122.             $slen = $Headers.ToLower().IndexOf("`nto: ")  
  123.             if($slen -gt 0){  
  124.                 $elen = $Headers.IndexOf("`r`n",$slen)                
  125.                 $ToAddressParsed = $Headers.SubString(($slen+4),$elen-($slen+4))  
  126.                 while($Headers.Substring($elen+2,1) -eq [char]9){  
  127.                     $slen = $elen+2  
  128.                     $elen = $Headers.IndexOf("`r`n",$slen)    
  129.                     $ToAddressParsed += $Headers.SubString(($slen),$elen-($slen))  
  130.                 }  
  131.                 $emailRegex = new-object System.Text.RegularExpressions.Regex("\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",[System.Text.RegularExpressions.RegexOptions]::IgnoreCase);  
  132.                 $emailMatches = $emailRegex.Matches($ToAddressParsed);  
  133.                 foreach($Match in $emailMatches){  
  134.                     if($mbAddress.Contains($Match.Value.Tolower())){  
  135.                         $Address = $Match  
  136.                         $Method = "To"  
  137.                         if($rptcntCount.ContainsKey($Match.Value)){  
  138.                             $rptcntCount[$Match.Value].To +=1  
  139.                         }  
  140.                         else{  
  141.                             $rptObj = "" | Select Address,To,CC,BCC  
  142.                             $rptObj.Address = $Match.Value  
  143.                             $rptObj.To = 1  
  144.                             $rptObj.CC = 0  
  145.                             $rptObj.BCC = 0  
  146.                             $rptcntCount.Add($Match.Value,$rptObj)                    
  147.                         }  
  148.                     }  
  149.                     else{  
  150.                         if($Address -eq ""){  
  151.                             $Address = $Match  
  152.                             $Method = "Other"  
  153.                         }  
  154.                     }  
  155.                 }  
  156.             }  
  157.             $slen = $Headers.ToLower().IndexOf("`ncc: ")  
  158.             if($slen -gt 0){  
  159.                 $elen = $Headers.IndexOf("`r`n",$slen)  
  160.                 $ccAddressParsed = $Headers.SubString(($slen+4),$elen-($slen+4))  
  161.                 while($Headers.Substring($elen+2,1) -eq [char]9){  
  162.                     $slen = $elen+2  
  163.                     $elen = $Headers.IndexOf("`r`n",$slen)    
  164.                     $ccAddressParsed += $Headers.SubString(($slen),$elen-($slen))  
  165.                 }  
  166.                 $emailRegex = new-object System.Text.RegularExpressions.Regex("\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",[System.Text.RegularExpressions.RegexOptions]::IgnoreCase);  
  167.                 $emailMatches = $emailRegex.Matches($ccAddressParsed);  
  168.                 foreach($Match in $emailMatches){  
  169.                     if($mbAddress.Contains($Match.Value.Tolower())){  
  170.                         $Address = $Match  
  171.                         $Method = "CC"  
  172.                         if($rptcntCount.ContainsKey($Match.Value)){  
  173.                             $rptcntCount[$Match.Value].CC +=1  
  174.                         }  
  175.                         else{  
  176.                             $rptObj = "" | Select Address,To,CC,BCC  
  177.                             $rptObj.Address = $Match.Value  
  178.                             $rptObj.To = 0  
  179.                             $rptObj.CC = 1  
  180.                             $rptObj.BCC = 0  
  181.                             $rptcntCount.Add($Match.Value,$rptObj)                    
  182.                         }  
  183.                     }  
  184.                 }  
  185.             }  
  186.             $slen = $Headers.ToLower().IndexOf("`nbcc: ")  
  187.             if($slen -gt 0){  
  188.                 $elen = $Headers.IndexOf("`r`n",$slen)  
  189.                 $bccAddressParsed = $Headers.SubString(($slen+4),$elen-($slen+4))  
  190.                 while($Headers.Substring($elen+2,1) -eq [char]9){  
  191.                     $slen = $elen+2  
  192.                     $elen = $Headers.IndexOf("`r`n",$slen)    
  193.                     $bccAddressParsed += $Headers.SubString(($slen),$elen-($slen))  
  194.                 }  
  195.                 $emailRegex = new-object System.Text.RegularExpressions.Regex("\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",[System.Text.RegularExpressions.RegexOptions]::IgnoreCase);  
  196.                 $emailMatches = $emailRegex.Matches($bccAddressParsed);  
  197.                 foreach($Match in $emailMatches){  
  198.                     if($mbAddress.Contains($Match.Value.Tolower())){  
  199.                         $Address = $Match  
  200.                         $Method = "BCC"  
  201.                         if($rptcntCount.ContainsKey($Match.Value)){  
  202.                             $rptcntCount[$Match.Value].BCC +=1  
  203.                         }  
  204.                         else{  
  205.                             $rptObj = "" | Select Address,To,CC,BCC  
  206.                             $rptObj.Address = $Match.Value  
  207.                             $rptObj.To = 0  
  208.                             $rptObj.CC = 0  
  209.                             $rptObj.BCC = 1  
  210.                             $rptcntCount.Add($Match.Value,$rptObj)                    
  211.                         }  
  212.                     }  
  213.                 }  
  214.             }  
  215.         }  
  216.         $ItemReport = "" | Select DateTimeReceived,Method,Address,Subject,Size  
  217.         $ItemReport.DateTimeReceived = $Item.DateTimeReceived  
  218.         $ItemReport.Subject = $Item.Subject  
  219.         $ItemReport.Size = $Item.Size  
  220.         $ItemReport.Method = $Method  
  221.         $ItemReport.Address = $Address  
  222.         $rptCollection += $ItemReport  
  223.           
  224.     }      
  225.     $ivItemView.Offset += $fiItems.Items.Count  
  226.     Write-Host ("Processed " + $ivItemView.Offset + " of " + $fiItems.TotalCount)  
  227. }while($fiItems.MoreAvailable -eq $true)   
  228. $rptcntCount.Values | ft  
  229. $rptcntCount.Values | Export-Csv -NoTypeInformation -path "c:\Temp\$MailboxName-MHSummaryReport.csv"  
  230. $rptCollection | Export-Csv -NoTypeInformation -path "c:\Temp\$MailboxName-MHItemReport.csv"  

Sending a No Reply, No ReplyAll, No Forward Email using EWS and Powershell

$
0
0
I've you haven't seen this before Gavin Smyth from Microsoft Research put together this cool VSTO plugin to allow you to send an Email from Outlook that will disable the Reply, ReplyAll and Forward Buttons on the Outlook ribbon http://research.microsoft.com/en-us/projects/researchdesktop/noreplyall.aspx and the how to posts about the VSTO http://blogs.msdn.com/b/gsmyth/archive/2011/09/18/noreply-vsto-add-in-wrap-up.aspx .

Note this only works in Outlook (not OWA or ActiveSync etc) but basically what a users would see when they receive a message is



How this works is it sets the PidLidVerbStream Property on a message which is mostly documented in the http://msdn.microsoft.com/en-us/library/ee218541(v=exchg.80).aspx protocol document. I say mostly because the format you get when using the methods from the NoReply Addin is a little different from the format documented which is for Voting buttons but its good enough to work with. The Verbs that this property refers to are the standard Verbs that MAPI implements which are documented in http://msdn.microsoft.com/en-us/library/office/cc815879(v=office.15).aspx .

So to use this same property in an EWS script you can just set it using the Extended Properties, the value you use is a little tricky as only the voteoption format is fully documented. But because this is pretty standard you can cut a past the Hex Values which contains the two streams from this property out of an existing message and then just change the bits that either enables or disable the Verbs for what you want to enable or disable on the Message your sending.

So this is what the following script does is allows you to send a message using EWS and set the options to enable and disable each of these verbs.

In the script I've got the following custom object where you can set the verbs on or off
  1. $VerbSetting = "" | Select DisableReplyAll,DisableReply,DisableForward,DisableReplyToFolder  
  2. $VerbSetting.DisableReplyAll = $true  
  3. $VerbSetting.DisableReply = $true  
  4. $VerbSetting.DisableForward = $true  
  5. $VerbSetting.DisableReplyToFolder = $true  

The rest of the script builds the VerbStream value property based on the setting in the custom object and the rest of the script is a standard EWS script to send a message.

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

  1. $MailboxName = $args[0]  
  2. $SentTo = $args[1]  
  3.   
  4. $VerbSetting = "" | Select DisableReplyAll,DisableReply,DisableForward,DisableReplyToFolder  
  5. $VerbSetting.DisableReplyAll = $true  
  6. $VerbSetting.DisableReply = $true  
  7. $VerbSetting.DisableForward = $true  
  8. $VerbSetting.DisableReplyToFolder = $true  
  9.   
  10. ## Load Managed API dll    
  11.   
  12. ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT  
  13. $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")  
  14. if (Test-Path $EWSDLL)  
  15.     {  
  16.     Import-Module $EWSDLL  
  17.     }  
  18. else  
  19.     {  
  20.     "$(get-date -format yyyyMMddHHmmss):"  
  21.     "This script requires the EWS Managed API 1.2 or later."  
  22.     "Please download and install the current version of the EWS Managed API from"  
  23.     "http://go.microsoft.com/fwlink/?LinkId=255472"  
  24.     ""  
  25.     "Exiting Script."  
  26.     exit  
  27.     }  
  28.     
  29. ## Set Exchange Version    
  30. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  31.     
  32. ## Create Exchange Service Object    
  33. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  34.     
  35. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  36.     
  37. #Credentials Option 1 using UPN for the windows Account    
  38. $psCred = Get-Credential    
  39. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  40. $service.Credentials = $creds        
  41.     
  42. #Credentials Option 2    
  43. #service.UseDefaultCredentials = $true    
  44.     
  45. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  46.     
  47. ## Code From http://poshcode.org/624  
  48. ## Create a compilation environment  
  49. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  50. $Compiler=$Provider.CreateCompiler()  
  51. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  52. $Params.GenerateExecutable=$False  
  53. $Params.GenerateInMemory=$True  
  54. $Params.IncludeDebugInformation=$False  
  55. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  56.   
  57. $TASource=@' 
  58.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  59.     public class TrustAll : System.Net.ICertificatePolicy { 
  60.       public TrustAll() {  
  61.       } 
  62.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  63.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  64.         System.Net.WebRequest req, int problem) { 
  65.         return true; 
  66.       } 
  67.     } 
  68.   } 
  69. '@   
  70. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  71. $TAAssembly=$TAResults.CompiledAssembly  
  72.   
  73. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  74. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  75. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  76.   
  77. ## end code from http://poshcode.org/624  
  78.     
  79. ## 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    
  80.     
  81. #CAS URL Option 1 Autodiscover    
  82. $service.AutodiscoverUrl($MailboxName,{$true})    
  83. "Using CAS Server : " + $Service.url     
  84.      
  85. #CAS URL Option 2 Hardcoded    
  86.     
  87. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  88. #$service.Url = $uri      
  89.     
  90. ## Optional section for Exchange Impersonation    
  91.     
  92. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  93.   
  94.   
  95. function Get-VerbStream{    
  96.     param (    
  97.             $VerbSettings = "$( throw 'VerbSettings is a mandatory Parameter' )"    
  98.           )    
  99.     process{    
  100.       
  101.     $Header = "02010400000000000000"  
  102.     $ReplyToAllHeader = "055265706C790849504D2E4E6F7465074D657373616765025245050000000000000000"  
  103.     $ReplyToAllFooter = "0000000000000002000000660000000200000001000000"  
  104.     $ReplyToHeader = "0C5265706C7920746F20416C6C0849504D2E4E6F7465074D657373616765025245050000000000000000"  
  105.     $ReplyToFooter = "0000000000000002000000670000000300000002000000"  
  106.     $ForwardHeader = "07466F72776172640849504D2E4E6F7465074D657373616765024657050000000000000000"  
  107.     $ForwardFooter = "0000000000000002000000680000000400000003000000"  
  108.     $ReplyToFolderHeader = "0F5265706C7920746F20466F6C6465720849504D2E506F737404506F737400050000000000000000"  
  109.     $ReplyToFolderFooter = "00000000000000020000006C00000008000000"  
  110.     $VoteOptionExtras = "0401055200650070006C00790002520045000C5200650070006C007900200074006F00200041006C006C0002520045000746006F007200770061007200640002460057000F5200650070006C007900200074006F00200046006F006C0064006500720000"  
  111.     if($VerbSetting.DisableReplyAll){  
  112.         $DisableReplyAllVal = "00"  
  113.     }  
  114.     else{  
  115.         $DisableReplyAllVal = "01"  
  116.     }  
  117.     if($VerbSetting.DisableReply){  
  118.         $DisableReplyVal = "00"  
  119.     }  
  120.     else{  
  121.         $DisableReplyVal = "01"  
  122.     }  
  123.     if($VerbSetting.DisableForward){  
  124.         $DisableForwardVal = "00"  
  125.     }  
  126.     else{  
  127.         $DisableForwardVal = "01"  
  128.     }  
  129.     if($VerbSetting.DisableReplyToFolder){  
  130.         $DisableReplyToFolderVal = "00"  
  131.     }  
  132.     else{  
  133.         $DisableReplyToFolderVal = "01"  
  134.     }  
  135.     $VerbValue = $Header +  $ReplyToAllHeader + $DisableReplyAllVal + $ReplyToAllFooter + $ReplyToHeader + $DisableReplyVal +$ReplyToFooter + $ForwardHeader + $DisableForwardVal + $ForwardFooter + $ReplyToFolderHeader + $DisableReplyToFolderVal + $ReplyToFolderFooter + $VoteOptionExtras  
  136.     return $VerbValue  
  137.     }  
  138. }  
  139.   
  140. function hex2binarray($hexString){  
  141.     $i = 0  
  142.     [byte[]]$binarray = @()  
  143.     while($i -le $hexString.length - 2){  
  144.         $strHexBit = ($hexString.substring($i,2))  
  145.         $binarray += [byte]([Convert]::ToInt32($strHexBit,16))  
  146.         $i = $i + 2  
  147.     }  
  148.     return ,$binarray  
  149. }  
  150.   
  151.   
  152.   
  153. $VerbStreamProp = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,0x8520, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)  
  154.   
  155. $VerbSettingValue = get-VerbStream $VerbSetting  
  156.   
  157. $EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service    
  158. $EmailMessage.Subject = "Message Subject"    
  159. #Add Recipients      
  160. $EmailMessage.ToRecipients.Add($SentTo)    
  161. $EmailMessage.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody    
  162. $EmailMessage.Body.BodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::HTML    
  163. $EmailMessage.Body.Text = "Body"   
  164. $EmailMessage.SetExtendedProperty($VerbStreamProp,(hex2binarray $VerbSettingValue))  
  165. $EmailMessage.SendAndSaveCopy()    

Searching based on Categories in EWS

$
0
0
The Categories or Keywords properties on a Mailbox Item is one of the more commonly used Item properties in Exchange. When you want to search for Items with a particular Category set it does present some challenges in EWS.

With EWS you have 3 different search methods, the first being Restrictions (or SearchFilter's if your using the Managed API) that work like MAPI restrictions although you can't build restriction that are 100% equivalent to what you can in MAPI. One particular case is with the Categories property, because this is a Multi-Valued property (or String Array) the IsEqual and Contains Restrictions wont work like the Sproperty restriction in MAPI http://msdn.microsoft.com/en-us/library/office/cc815385(v=office.15).aspx

So the next type of Search you can do is a Search of a Mailbox folder using an AQS querystring which essentially does an Index search. Because the Categories property is an Indexed property this will work fine for Categories. The thrid type of Search you can do is eDiscovery in Exchange 2013 which I'll cover in another post

The following script does a search of the the Inbox folder for a particular Keyword using the AQS query

"System.Category:Categorytolookfor"

It also does a client side validation to ensure no false positives are included. To run the script just pass in the name of the mailbox you want to search and the Category to search for (enclosed in '' if there is a space) eg .\SearchCategory.ps1 glen@domain.com 'green category' . The script will output a CSV report of all the messages founds with that particular Category set.

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

  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2.   
  3. $MailboxName = $args[0]  
  4. $CategoryToFind = $args[1]  
  5.   
  6. ## Load Managed API dll    
  7.   
  8. ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT  
  9. $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")  
  10. if (Test-Path $EWSDLL)  
  11.     {  
  12.     Import-Module $EWSDLL  
  13.     }  
  14. else  
  15.     {  
  16.     "$(get-date -format yyyyMMddHHmmss):"  
  17.     "This script requires the EWS Managed API 1.2 or later."  
  18.     "Please download and install the current version of the EWS Managed API from"  
  19.     "http://go.microsoft.com/fwlink/?LinkId=255472"  
  20.     ""  
  21.     "Exiting Script."  
  22.     exit  
  23.     }  
  24.     
  25. ## Set Exchange Version    
  26. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  27.     
  28. ## Create Exchange Service Object    
  29. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  30.     
  31. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  32.     
  33. #Credentials Option 1 using UPN for the windows Account    
  34. $psCred = Get-Credential    
  35. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  36. $service.Credentials = $creds        
  37.     
  38. #Credentials Option 2    
  39. #service.UseDefaultCredentials = $true    
  40.     
  41. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  42.     
  43. ## Code From http://poshcode.org/624  
  44. ## Create a compilation environment  
  45. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  46. $Compiler=$Provider.CreateCompiler()  
  47. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  48. $Params.GenerateExecutable=$False  
  49. $Params.GenerateInMemory=$True  
  50. $Params.IncludeDebugInformation=$False  
  51. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  52.   
  53. $TASource=@' 
  54.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  55.     public class TrustAll : System.Net.ICertificatePolicy { 
  56.       public TrustAll() {  
  57.       } 
  58.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  59.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  60.         System.Net.WebRequest req, int problem) { 
  61.         return true; 
  62.       } 
  63.     } 
  64.   } 
  65. '@   
  66. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  67. $TAAssembly=$TAResults.CompiledAssembly  
  68.   
  69. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  70. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  71. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  72.   
  73. ## end code from http://poshcode.org/624  
  74.     
  75. ## 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    
  76.     
  77. #CAS URL Option 1 Autodiscover    
  78. $service.AutodiscoverUrl($MailboxName,{$true})    
  79. "Using CAS Server : " + $Service.url     
  80.      
  81. #CAS URL Option 2 Hardcoded    
  82.     
  83. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  84. #$service.Url = $uri      
  85.     
  86. ## Optional section for Exchange Impersonation    
  87.     
  88. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  89.   
  90. $AQSString = "System.Category:`"" + $CategoryToFind + "`""  
  91. # Bind to the Inbox Folder  
  92. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  93. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  94. $rptCollection = @()  
  95. #Define ItemView to retrive just 1000 Items      
  96. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)      
  97. $fiItems = $null      
  98. do{      
  99.     $fiItems = $service.FindItems($Inbox.Id,$AQSString,$ivItemView)      
  100.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
  101.     foreach($Item in $fiItems.Items){  
  102.         #Validate exact Category Match  
  103.         $match = $false  
  104.         foreach($cat in $Item.Categories){  
  105.             if($cat.ToLower() -eq $CategoryToFind.ToLower()){  
  106.                 $match = $true;  
  107.             }  
  108.         }  
  109.         if($match){  
  110.             $rptObj = "" | Select DateTimeReceived,From,Subject,Size,Categories    
  111.             $rptObj.DateTimeReceived = $Item.DateTimeReceived    
  112.             $rptObj.From  = $Item.From.Name    
  113.             $rptObj.Subject = $Item.Subject    
  114.             $rptObj.Size = $Item.Size  
  115.             $rptObj.Categories = [system.String]::Join(",",$Item.Categories)  
  116.             $rptCollection += $rptObj  
  117.         }  
  118.     }      
  119.     $ivItemView.Offset += $fiItems.Items.Count      
  120. }while($fiItems.MoreAvailable -eq $true)   
  121. $rptCollection | Export-Csv -NoTypeInformation -Path c:\temp\$MailboxName-CategoryReport.csv  
  122. Write-Host "Report Saved to c:\temp\$MailboxName-CategoryReport.csv"   


A look into Conversations with EWS and Powershell in a Mailbox

$
0
0
Conversation views in email clients have become par for the course these days, while I'm personally not a great fan it can be a useful feature to use when looking at Mailbox data. With EWS from Exchange 2010 you can use the dedicated EWS operations which are documented http://msdn.microsoft.com/en-us/library/office/dn610351(v=exchg.150).aspx . In Exchange 2013 the conversation operations have been enhanced to offer more functionality such as the ability to use an AQS QueryString to filter the results plus also the ability to apply actions to conversation like applying categories or a specific retention tag.

One interesting thing you can apply these conversation operations to is looking at Mailbox statistics in a different way by looking at the operation of Mailboxes to see how engaged the owners or users of the Mailboxes are, by seeing how engaged in the conversation that are happening in the Mailbox. eg


The FindConversation operation in Exchange will return information such as the MessageCount and Size in a particular folder as well as the GlobalCount and Sizes across all folders in a Mailbox for a conversation.This is sample of a script that uses the findconversation operation to do some statistical sampling of conversation data. So what this does is

  • Does a FindConversation on the Inbox folder to grab the conversation information for the Inbox
  • Does a FindConversation on the SentItems Folder to grab the conversation stats for the Sent Mail
  • Combines the results together to work out the participation rate in the conversation based on the amount of Messages this particular mailbox sent.
  • For Exchange 2013 you can also query based on the received date of the email so you can look at the last 7 days worth or data etc.
So you can run the script like this to examine the whole of the Inbox

 .\converstats.ps1 user@domain.onmicrosoft.com

or you can use a Date to specify how much data you want to look at eg for the last 7 days use

.\converstats.ps1 user@domain.onmicrosoft.com (get-date).AddDays(-7)

In the script itself I've got it set to only report on conversation with a messagecount greater then 3 and sort the conversation by the participation rate you can adjust this by fiddling with the following line

$Script:rptcollection.Values | Where-Object {$_.InboxMessageCount -gt 3} | Sort-ObjectParticipationRate-Descending | Export-Csv-NoTypeInformation-Pathc:\Temp\$MailboxName-cnvStats.csv

I've put a download this script here the script itself looks like
  1. $MailboxName = $args[0]  
  2. $StartDate = $args[1]  
  3.   
  4.   
  5. ## Get the Mailbox to Access from the 1st commandline argument  
  6.   
  7. ## Load Managed API dll    
  8.   
  9. ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT  
  10. $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")  
  11. if (Test-Path $EWSDLL)  
  12.     {  
  13.     Import-Module $EWSDLL  
  14.     }  
  15. else  
  16.     {  
  17.     "$(get-date -format yyyyMMddHHmmss):"  
  18.     "This script requires the EWS Managed API 1.2 or later."  
  19.     "Please download and install the current version of the EWS Managed API from"  
  20.     "http://go.microsoft.com/fwlink/?LinkId=255472"  
  21.     ""  
  22.     "Exiting Script."  
  23.     exit  
  24.     }  
  25.     
  26. ## Set Exchange Version    
  27. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1  
  28.     
  29. ## Create Exchange Service Object    
  30. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  31.     
  32. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  33.     
  34. #Credentials Option 1 using UPN for the windows Account    
  35. $psCred = Get-Credential    
  36. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  37. $service.Credentials = $creds        
  38.     
  39. #Credentials Option 2    
  40. #service.UseDefaultCredentials = $true    
  41.     
  42. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  43.     
  44. ## Code From http://poshcode.org/624  
  45. ## Create a compilation environment  
  46. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  47. $Compiler=$Provider.CreateCompiler()  
  48. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  49. $Params.GenerateExecutable=$False  
  50. $Params.GenerateInMemory=$True  
  51. $Params.IncludeDebugInformation=$False  
  52. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  53.   
  54. $TASource=@' 
  55.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  56.     public class TrustAll : System.Net.ICertificatePolicy { 
  57.       public TrustAll() {  
  58.       } 
  59.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  60.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  61.         System.Net.WebRequest req, int problem) { 
  62.         return true; 
  63.       } 
  64.     } 
  65.   } 
  66. '@   
  67. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  68. $TAAssembly=$TAResults.CompiledAssembly  
  69.   
  70. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  71. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  72. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  73.   
  74. ## end code from http://poshcode.org/624  
  75.     
  76. ## 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    
  77.     
  78. #CAS URL Option 1 Autodiscover    
  79. $service.AutodiscoverUrl($MailboxName,{$true})    
  80. "Using CAS Server : " + $Service.url     
  81.      
  82. #CAS URL Option 2 Hardcoded    
  83.     
  84. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  85. #$service.Url = $uri      
  86.     
  87. ## Optional section for Exchange Impersonation    
  88.     
  89. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  90. if($StartDate -ne $null){  
  91.     $AQSString = "received:>" + $StartDate.ToString("yyyy-MM-dd")   
  92.     $AQSString  
  93. }  
  94. # Bind to the Inbox Folder  
  95. $Script:rptcollection = @{}  
  96. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  97. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  98. # Bind to the SentItems Folder  
  99. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$MailboxName)     
  100. $SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  101.   
  102. function Process-Folder{    
  103.     param (    
  104.             $FolderId = "$( throw 'SMTPAddress is a mandatory Parameter' )",  
  105.             $IsSentItems = "$( throw 'SMTPAddress is a mandatory Parameter' )"  
  106.           )    
  107.     process{   
  108.         $cnvItemView = New-Object Microsoft.Exchange.WebServices.Data.ConversationIndexedItemView(1000)  
  109.         $cnvs = $null;  
  110.         do  
  111.         {  
  112.             $cnvs = $service.FindConversation($cnvItemView$FolderId,$AQSString);  
  113.             "Number of Conversation returned " + $cnvs.Count  
  114.             foreach($cnv in $cnvs){  
  115.                 $rptobj = $cnv | select Topic,LastDeliveryTime,UniqueSenders,UniqueRecipients,InboxMessageCount,GlobalMessageCount,InboxMessageSize,SentItemsMessageCount,SentItemsMessageSize,ParticipationRate      
  116.                 if($Script:rptcollection.Contains($cnv.Id.UniqueId)-eq $false){  
  117.                     if($IsSentItems){  
  118.                         $rptobj.SentItemsMessageCount = $cnv.MessageCount  
  119.                         $rptobj.SentItemsMessageSize = $cnv.Size  
  120.                         $rptobj.InboxMessageCount = 0  
  121.                         $rptobj.InboxMessageSize = 0  
  122.                         $rptobj.LastDeliveryTime = $cnv.LastDeliveryTime  
  123.                         $rptobj.UniqueSenders = $cnv.GlobalUniqueSenders  
  124.                         $rptobj.UniqueRecipients = $cnv.GlobalUniqueRecipients  
  125.                     }  
  126.                     else{  
  127.                         $rptobj.InboxMessageCount = $cnv.MessageCount  
  128.                         $rptobj.InboxMessageSize = $cnv.Size  
  129.                         $rptobj.SentItemsMessageCount = 0  
  130.                         $rptobj.SentItemsMessageSize = 0  
  131.                         $rptobj.LastDeliveryTime = $cnv.LastDeliveryTime  
  132.                         $rptobj.UniqueSenders = $cnv.GlobalUniqueSenders  
  133.                         $rptobj.UniqueRecipients = $cnv.GlobalUniqueRecipients  
  134.                     }  
  135.                     $Script:rptcollection.Add($cnv.Id.UniqueId,$rptobj)  
  136.                 }  
  137.                 else{  
  138.                     if($IsSentItems){  
  139.                         $Script:rptcollection[$cnv.Id.UniqueId].SentItemsMessageCount = $cnv.MessageCount  
  140.                         $Script:rptcollection[$cnv.Id.UniqueId].SentItemsMessageSize = $cnv.Size  
  141.                     }  
  142.                     else{  
  143.                         $Script:rptcollection[$cnv.Id.UniqueId].InboxMessageCount = $cnv.MessageCount  
  144.                         $Script:rptcollection[$cnv.Id.UniqueId].InboxMessageSize = $cnv.Size  
  145.                     }  
  146.                        
  147.                 }                 
  148.               
  149.             }  
  150.             $cnvItemView.Offset += $cnvs.Count  
  151.         }while($cnvs.Count -gt 0)  
  152.     }  
  153. }  
  154. Process-Folder -FolderId $Inbox.Id -IsSentItems $false  
  155. Process-Folder -FolderId $SentItems.Id -IsSentItems $true  
  156. foreach($value in $Script:rptcollection.Values){  
  157.     if($value.GlobalMessageCount -gt 0 -band $value.SentItemsMessageCount -gt 0){  
  158.         $value.ParticipationRate = [Math]::round((($value.SentItemsMessageCount/$value.GlobalMessageCount) * 100))  
  159.     }  
  160.     else{  
  161.         $value.ParticipationRate = 0  
  162.     }  
  163. }  
  164. $Script:rptcollection.Values | Where-Object {$_.InboxMessageCount -gt 3} | Sort-Object ParticipationRate -Descending | Export-Csv -NoTypeInformation -Path c:\Temp\$MailboxName-cnvStats.csv  
  165.    

Creating a new folder in EWS and add it to the MyContacts list in OWA in Exchange 2013/Office365

$
0
0
The peoples hub is one of the new features in Exchange 2013 and Exchange Online that is aimed at making your life richer. One little quirk if your creating new folders using EWS is that they won't appear in the My Contacts list inOWA eg if I was creating a folder named aNewContactsFolder you would get


To make the new Folder you just created appear in the MyContacts list rather then other contacts you need to set these two yet to be documented properties

PeopleHubSortGroupPriority 
PeopleHubSortGroupPriorityVersion

Setting the value of these two named properties to 5 and 2 respectively will make the folder appear under My Contacts, if you want to move the folder into other contacts set the values to 10 and 2

Here's a sample script to create a New Folder as a subfolder of a Mailboxes Contacts Folder and set these two properties so the Contact Folder appears under MyContacts

To run the script pass the emailaddress of the mailbox you want to run it against as the first parameter and the foldername as the second parameter eg use something like .\createMyContactsFldv2.ps1 jcool@domain.com newfolderss

I've put a download of this script here the script itself looks like.
  1. ## Get the Mailbox to Access from the 1st commandline argument  
  2. $NewFolderName = $args[1]  
  3. $MailboxName = $args[0]  
  4.   
  5. ## Load Managed API dll    
  6.   
  7. ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT  
  8. $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")  
  9. if (Test-Path $EWSDLL)  
  10.     {  
  11.     Import-Module $EWSDLL  
  12.     }  
  13. else  
  14.     {  
  15.     "$(get-date -format yyyyMMddHHmmss):"  
  16.     "This script requires the EWS Managed API 1.2 or later."  
  17.     "Please download and install the current version of the EWS Managed API from"  
  18.     "http://go.microsoft.com/fwlink/?LinkId=255472"  
  19.     ""  
  20.     "Exiting Script."  
  21.     exit  
  22.     }  
  23.     
  24. ## Set Exchange Version    
  25. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  26.     
  27. ## Create Exchange Service Object    
  28. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  29.     
  30. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  31.     
  32. #Credentials Option 1 using UPN for the windows Account    
  33. $psCred = Get-Credential    
  34. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  35. $service.Credentials = $creds        
  36.     
  37. #Credentials Option 2    
  38. #service.UseDefaultCredentials = $true    
  39.     
  40. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  41.     
  42. ## Code From http://poshcode.org/624  
  43. ## Create a compilation environment  
  44. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  45. $Compiler=$Provider.CreateCompiler()  
  46. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  47. $Params.GenerateExecutable=$False  
  48. $Params.GenerateInMemory=$True  
  49. $Params.IncludeDebugInformation=$False  
  50. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  51.   
  52. $TASource=@' 
  53.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  54.     public class TrustAll : System.Net.ICertificatePolicy { 
  55.       public TrustAll() {  
  56.       } 
  57.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  58.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  59.         System.Net.WebRequest req, int problem) { 
  60.         return true; 
  61.       } 
  62.     } 
  63.   } 
  64. '@   
  65. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  66. $TAAssembly=$TAResults.CompiledAssembly  
  67.   
  68. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  69. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  70. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  71.   
  72. ## end code from http://poshcode.org/624  
  73.     
  74. ## 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    
  75.     
  76. #CAS URL Option 1 Autodiscover    
  77. $service.AutodiscoverUrl($MailboxName,{$true})    
  78. "Using CAS Server : " + $Service.url     
  79.      
  80. #CAS URL Option 2 Hardcoded    
  81.     
  82. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  83. #$service.Url = $uri      
  84.     
  85. ## Optional section for Exchange Impersonation    
  86.     
  87. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  88.   
  89.   
  90. #PropDefs   
  91. $PeopleHubSortGroupPriority = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common, "PeopleHubSortGroupPriority", [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);  
  92. $PeopleHubSortGroupPriorityVersion = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common, "PeopleHubSortGroupPriorityVersion", [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);  
  93.   
  94. # Bind to the Contacts Folder  
  95.   
  96. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$MailboxName)     
  97. $Contacts = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  98.   
  99. $NewFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service)    
  100. $NewFolder.DisplayName = $NewFolderName    
  101. $NewFolder.FolderClass = "IPF.Contact"  
  102. $NewFolder.SetExtendedProperty($PeopleHubSortGroupPriority,5);  
  103. $NewFolder.SetExtendedProperty($PeopleHubSortGroupPriorityVersion,2);  
  104. $NewFolder.Save($Contacts.Id)    
  105. "Created Folder"  

Advanced Search Folder creation with EWS and Powershell

$
0
0
In the past I've posted some examples of creating SearchFolders in Exchange with EWS and Powershell in the following post,  in this post I want to cover some more advanced things you can do with EWS and Search Folders.

The first thing I want to cover is how you change the option on a Search folder from "Show number of unread items" which is the default when you create a search folder in EWS to "Show total number of items". eg




To change this option you need to set the PidTagExtendedFolderFlags property which is documented in http://msdn.microsoft.com/en-us/library/office/cc839880(v=office.15).aspx . This is a binary property and you set this particular option by changing bit 6-7 in this property. Because you also need the SearchFolderGuid in this property the easiest time is to set this is right after you create the folder. eg
  1. $searchFolder.Save($sfRoot.Id);  
  2. $PR_EXTENDED_FOLDER_FLAGS = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x36DA, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)  
  3. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  4. $psPropset.Add($PR_EXTENDED_FOLDER_FLAGS)  
  5. $searchFolder.Load($psPropset)  
  6. $exVal = $null  
  7. if($SearchFolder.TryGetProperty($PR_EXTENDED_FOLDER_FLAGS,[ref]$exVal)){  
  8.      $newVal = "010402001000" + [System.BitConverter]::ToString($exVal).Replace("-","")  
  9.      $byteVal = HexStringToByteArray($newVal)  
  10.      $SearchFolder.SetExtendedProperty($PR_EXTENDED_FOLDER_FLAGS,$byteVal)  
  11.      $searchFolder.Update()  
  12. }  
  13. "Folder Created"  

Some other interesting things you can do when using EWS to create search Folders is you can filter on some of the more advanced Item properties you can't access normally using Outlook. For example the PidTagLastVerbExecuted can be used to create a Search folder to find all email where the last action taken on the Message was to reply to the Sender. Eg this would create a SearchFolder with this type of filter
  1. $PR_LAST_VERB_EXECUTED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x1081, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)    
  2. $sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_LAST_VERB_EXECUTED,102)   
  3. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  4. $SearchFolder = new-object Microsoft.Exchange.WebServices.Data.SearchFolder($service);  
  5. $rfRootFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)     
  6. $searchFolder.SearchParameters.RootFolderIds.Add($rfRootFolderId);  
  7. $searchFolder.SearchParameters.Traversal = [Microsoft.Exchange.WebServices.Data.SearchFolderTraversal]::Deep;  
  8. $searchFolder.SearchParameters.SearchFilter = $sfItemSearchFilter  
  9. $searchFolder.DisplayName = $SearchFilterName  
  10. $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SearchFolders,$MailboxName)     
  11. $sfRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  12. $searchFolder.Save($sfRoot.Id);  

Another interesting Search Folder you can create on 2013/Office365 is using the LatestWordCount property which isn't a documented property but you can use it to produce a SearchFolder of all your wordy emails eg the following would create a SearchFolder of email where this property indicated the word count of the message was greater than 1000 words.
  1. [Guid]$psguid = "23239608-685D-4732-9C55-4C95CB4E8E33"  
  2. $LatestMessageWordCount = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($psguid,"LatestMessageWordCount", [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)    
  3. $sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan($LatestMessageWordCount,1000)   
  4. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  5. $SearchFolder = new-object Microsoft.Exchange.WebServices.Data.SearchFolder($service);  
  6. $rfRootFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)     
  7. $searchFolder.SearchParameters.RootFolderIds.Add($rfRootFolderId);  
  8. $searchFolder.SearchParameters.Traversal = [Microsoft.Exchange.WebServices.Data.SearchFolderTraversal]::Deep;  
  9. $searchFolder.SearchParameters.SearchFilter = $sfItemSearchFilter  
  10. $searchFolder.DisplayName = $SearchFilterName  
  11. $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SearchFolders,$MailboxName)     
  12. $sfRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  13. $searchFolder.Save($sfRoot.Id);  

I've put a download of full sample scripts for creating search folder for these two examples here

Dumping out the MailTips for all the recipients using EWS and Powershell

$
0
0
MailTip's are a feature in Exchange from 2010 that gives you extra information when sending a message. In a pervious post I showed how you can use these to get the Outlook of Office status for a user using MailTips in EWS. In this Post I have a sample for going through and dumping out all the MailTips for all the Recipients in Exchange (that are feed in using Get-Recipient to include Contacts, Distribution list and any other type of recipeint) and produces an output like
 
 

Because there are no classes in the EWS Managed API to use MailTips so I'm just using Raw SOAP to send the MailTips request and then parse the XML result that comes back. With MailTips you can only request a max of 100 results at a time so there is some code to split the request up in batches of 100 if there are more the 100 entries.

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

## Get the Mailbox to Access from the 1st commandline argument

$MailboxName = $args[0]

$Script:rptCollection = @()


###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
$EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
if (Test-Path $EWSDLL)
{
Import-Module $EWSDLL
}
else
{
"$(get-date -format yyyyMMddHHmmss):"
"This script requires the EWS Managed API 1.2 or later."
"Please download and install the current version of the EWS Managed API from"
"http://go.microsoft.com/fwlink/?LinkId=255472"
""
"Exiting Script."
exit
}

## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1

## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)

## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials

#Credentials Option 1 using UPN for the windows Account
$psCred = Get-Credential
$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
$service.Credentials = $creds

#Credentials Option 2
#service.UseDefaultCredentials = $true

## Choose to ignore any SSL Warning issues caused by Self Signed Certificates

## 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

## 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

#CAS URL Option 1 Autodiscover
$service.AutodiscoverUrl($MailboxName,{$true})
"Using CAS Server : " + $Service.url

function DumpMailTips{
param (
$Mailboxes = "$( throw 'Folder Path is a mandatory Parameter' )"
)
process{

$expRequest = @"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header><RequestServerVersion Version="Exchange2010_SP1" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap:Header>
<soap:Body>
<GetMailTips xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
<SendingAs>
<EmailAddress xmlns="http://schemas.microsoft.com/exchange/services/2006/types">$MailboxName</EmailAddress>
</SendingAs>
<Recipients>
"@

foreach($mbMailboxin$Mailboxes){
$expRequest = $expRequest + "<Mailbox xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`"><EmailAddress>$mbMailbox</EmailAddress></Mailbox>"
}

$expRequest = $expRequest + "</Recipients><MailTipsRequested>All</MailTipsRequested></GetMailTips></soap:Body></soap:Envelope>"
$mbMailboxFolderURI = New-Object System.Uri($service.url)
$wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)
$wrWebRequest.KeepAlive = $false;
$wrWebRequest.Headers.Set("Pragma", "no-cache");
$wrWebRequest.Headers.Set("Translate", "f");
$wrWebRequest.Headers.Set("Depth", "0");
$wrWebRequest.ContentType = "text/xml";
$wrWebRequest.ContentLength = $expRequest.Length;
$wrWebRequest.CookieContainer = New-Object System.Net.CookieContainer
$wrWebRequest.Timeout = 60000;
$wrWebRequest.Method = "POST";
$wrWebRequest.Credentials = $creds
$bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($expRequest);
$wrWebRequest.ContentLength = $bqByteQuery.Length;
$rsRequestStream = $wrWebRequest.GetRequestStream();
$rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);
$rsRequestStream.Close();
$wrWebResponse = $wrWebRequest.GetResponse();
$rsResponseStream = $wrWebResponse.GetResponseStream()
$sr = new-object System.IO.StreamReader($rsResponseStream);
$rdResponseDocument = New-Object System.Xml.XmlDocument
$rdResponseDocument = New-Object System.Xml.XmlDocument
$rdResponseDocument.LoadXml($sr.ReadToEnd());
$Datanodes = @($rdResponseDocument.getElementsByTagName("m:MailTips"))

foreach($nodeValin$Datanodes){
$rptObj = "" | Select RecipientAddress,RecipientTypeDetails,PendingMailTips,OutOfOffice,CustomMailTip,MailboxFull,TotalMemberCount,ExternalMemberCount,MaxMessageSize,DeliveryRestricted,IsModerated
$rptObj.RecipientAddress = $nodeVal.RecipientAddress.EmailAddress
$rptObj.RecipientTypeDetails = $Script:emAray[$rptObj.RecipientAddress.ToLower()]
$rptObj.PendingMailTips = $nodeVal.PendingMailTips."#text"
$rptObj.OutOfOffice = $nodeVal.OutOfOffice.ReplyBody.Message
$rptObj.CustomMailTip = $nodeVal.CustomMailTip."#text"
$rptObj.MailboxFull = $nodeVal.MailboxFull."#text"
$rptObj.TotalMemberCount = $nodeVal.TotalMemberCount."#text"
$rptObj.ExternalMemberCount = $nodeVal.ExternalMemberCount."#text"
$rptObj.MaxMessageSize = $nodeVal.MaxMessageSize."#text"
$rptObj.DeliveryRestricted = $nodeVal.DeliveryRestricted."#text"
$rptObj.IsModerated = $nodeVal.IsModerated."#text"
$Script:rptCollection += $rptObj
}

}
}
$mbscn = @()
$Script:emAray = @{}
$rcps = Get-Recipient -ResultSize Unlimited
$rcps | ForEach-Object {
$Script:emAray.Add($_.PrimarySMTPAddress.ToString().ToLower(),$_.RecipientTypeDetails)
$mbscn += $_.PrimarySMTPAddress.ToString()
if($mbscn.count -gt 100){
dumpmailtips -Mailboxes $mbscn
$mbscn = @()
}
}
if($mbscn.count -gt 0){
dumpmailtips -Mailboxes $mbscn
}
$Script:rptCollection | Export-Csv -Path c:\temp\MailTipsDump.csv -NoTypeInformation

Accessing the Clutter Folder with EWS in Office365\Exchange Online

$
0
0
To start off the new year I thought I'd look at how you can go about using some of the new features that are being introduced in Exchange Online. One of the big new features is Clutter which we all saw at MEC is Austin and is now being rolled out to Office365 tenants. If you don't know what clutter is check out http://blogs.office.com/2014/11/11/de-clutter-inbox-office-365/ but basically it is Machine learning (AI or skynet for the paranoid) for your Mailbox. Machine learning is one of the fruits of increasing processing power and also the cloud in that software development/rollout cycles are now more closely aligned to what hardware can do. Keep in mind this is just the start of what the technology can do, there is just so far tweaking UI's can go so this to me is where the really exciting future of the tech is and I'm looking forward to see where this goes in the coming year.

Back to the subject at hand when you enable Clutter you end up with a new folder in your Mailbox that hangs of the MsgRoot called Clutter (or the localized equivalent). While there are a number of new objects for Clutter in the new Proxy definitions in EWS there is no Distinguishedfolderid for the Clutter folder like most other Mailbox system folders. So to get the Clutter folder in EWS you will need to either search for it by name of a better way is you can use the ClutterFolderEntryId extended property which is available on the Mailbox's Non_ipm_root folder. When you request this property you will get returned the HexEntryId of the Clutter folder which you then need to convert using the ConvertId operation to an EWSId. You then will be able to bind to the folder as per usual. To put this together in a particular sample here are two example of doing this in c# and powershell. The following will get the Clutter folder and showing the number of Unread Email in this folder.

c#
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
            ExtendedPropertyDefinition ClutterFolderEntryId = new ExtendedPropertyDefinition(new Guid("{23239608-685D-4732-9C55-4C95CB4E8E33}"), "ClutterFolderEntryId", MapiPropertyType.Binary);
PropertySet iiips = new PropertySet();
iiips.Add(ClutterFolderEntryId);
String MailboxName = "jcool@domain.com";
FolderId FolderRootId = new FolderId(WellKnownFolderName.Root, MailboxName);
Folder FolderRoot = Folder.Bind(service, FolderRootId, iiips);
Byte[] FolderIdVal = null;
if (FolderRoot.TryGetProperty(ClutterFolderEntryId, out FolderIdVal))
{
AlternateId aiId = new AlternateId(IdFormat.HexEntryId, BitConverter.ToString(FolderIdVal).Replace("-", ""), MailboxName);
AlternateId ConvertedId = (AlternateId)service.ConvertId(aiId, IdFormat.EwsId);
Folder ClutterFolder = Folder.Bind(service, new FolderId(ConvertedId.UniqueId));
Console.WriteLine("Unread Email in clutter : " + ClutterFolder.UnreadCount);
}

Powershell
  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
## Get the Mailbox to Access from the 1st commandline argument

$MailboxName = $args[0]

## Load Managed API dll

###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
$EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
if (Test-Path$EWSDLL)
{
Import-Module$EWSDLL
}
else
{
"$(get-date -format yyyyMMddHHmmss):"
"This script requires the EWS Managed API 1.2 or later."
"Please download and install the current version of the EWS Managed API from"
"http://go.microsoft.com/fwlink/?LinkId=255472"
""
"Exiting Script."
exit
}

## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2

## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)

## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials

#Credentials Option 1 using UPN for the windows Account
$psCred = Get-Credential
$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
$service.Credentials = $creds

#Credentials Option 2
#service.UseDefaultCredentials = $true

## Choose to ignore any SSL Warning issues caused by Self Signed Certificates

## 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

## 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

#CAS URL Option 1 Autodiscover
$service.AutodiscoverUrl($MailboxName,{$true})
"Using CAS Server : " + $Service.url

#CAS URL Option 2 Hardcoded

#$uri=[system.URI] "https://casservername/ews/exchange.asmx"
#$service.Url = $uri

## Optional section for Exchange Impersonation

#$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)

function ConvertId{
param (
$HexId = "$( throw 'HexId is a mandatory Parameter' )"
)
process{
$aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId
$aiItem.Mailbox = $MailboxName
$aiItem.UniqueId = $HexId
$aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId
$convertedId = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EwsId)
return$convertedId.UniqueId
}
}

# Bind to the Root Folder
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$ClutterFolderEntryId = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([System.Guid]::Parse("{23239608-685D-4732-9C55-4C95CB4E8E33}"), "ClutterFolderEntryId", [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($ClutterFolderEntryId)
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)
$FolderIdVal = $null
if ($RootFolder.TryGetProperty($ClutterFolderEntryId,[ref]$FolderIdVal))
{
$Clutterfolderid= new-object Microsoft.Exchange.WebServices.Data.FolderId((ConvertId -HexId ([System.BitConverter]::ToString($FolderIdVal).Replace("-",""))))
$ClutterFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$Clutterfolderid);
"Unread Email in clutter : " + $ClutterFolder.UnreadCount
}
else{
"Clutter Folder not found"
}

Viewing all 241 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>