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

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"
}

Setting properties via EWS on a Draft message is a compose Mail App

$
0
0
With the latest updates that have been released recently for Compose apps that are now available in OWA in Office365 and the Office/Exchange 16 preview the ability to extend what your compose apps can do has been greatly enhanced. With version 1.3 of the API you can now save an Item via the saveAsync method https://msdn.microsoft.com/en-us/library/office/mt269096.aspx .

Why this small change is important from an extensibility point of view is that when you save an Item this way, it creates a draft in the Mailbox you activated the compose app from which you can then modify anyway you like using the EWS updateItem operation. (normally you would be restricted in a compose app to just using the subset of properties and methods provided for in the Office.js).

One trick however is the ItemId which you need to make an UpdateItem EWS Request to update the Draft Item is still only available in Read Mode. So to get around this I've come up with the following workaround

  1. Set a custom property on the draft Item using the Mail App Custom properties method using something like https://msdn.microsoft.com/EN-US/library/office/fp142263.aspx . With my Mail Apps I generate a new GUID for the app session that I use as the value.
  2. Save the Item your composing as a Draft using saveAync
  3. Now you can use makeEwsRequestAsync to do a findItems on the drafts folder of the Mailbox with a restriction so it only finds the Item with the Custom property you have set. The Custom properties you can set in the Mail App API are documented in https://msdn.microsoft.com/en-us/library/hh968549(v=exchg.80).aspx . But basically what you have is they are a named property within the PS_PUBLIC_STRING propset of type String. The property name of the property is prefixed with cecp- and rest of the property name is the GUID of your Mail Apps as defined in the application's manifest Id .
  4. Once you have the ItemId and Change key you can then use UpdateItem in EWS to update any of the extended or strongly type properties on the draft Message.
One thing to note to use this workaround your Mail App needs the ReadWriteMailbox privilege to allow the use of the EWS Operations. I've put together a sample of using this workaround in my OWA Voting button app.

Also because this is using version 1.3 of the Mail App Api you should set the requirements in the Manifest file of your application eg

<Requirements>
<SetsDefaultMinVersion="1.3">
<SetMinVersion="1.3"Name="Mailbox"></Set>
</Sets>
</Requirements>

This will mean your Mail App will only activate in clients that support version 1.3 which means at the moment it won't activate in Outlook 2013.

OWA Voting Button Compose App for Office365/Exchange 2016

$
0
0
OWA or Outlook on the Web have never quite had the feature parity with the desktop version of Outlook which can be a point of frustration for those primarily using it. Although interesting in Office365 new features like clutter, sweep etc are now lighting up in the OWA first before they make their way into new desktop releases. One of the things you haven't been able to do previously with OWA is create an Email with voting buttons. With compose apps and some of the recent changes in version 1.3 of the API which I posted about here, you now have the ability to add some of this functionality back in. Actually with a little imagination you also have the ability to actually build something a lot better then the voting buttons feature which have been around in Exchange for some time. But in this post I want to show how you can create a Compose Mail App that uses EWS to make changes to the draft message to enable the feature.

One thing also to note is that creating an Email with Voting buttons isn't supported also in EWS  so I'm using a workaround of generating the PidLidVerbStream property value using the documentation from https://msdn.microsoft.com/en-us/library/ee218541(v=exchg.80).aspx . The one issue you can have is if you don't get the value of this property correct this can cause Outlook to crash (which means you then need to delete the offending item with mfcMapi). So this Mail App would be unsupported, experimental and largely untested.

Here's a screenshot of what the Apps looks like in Action


After you select the Voting option you want you need to hit the Save button which will invoke the actions to modify the draft message.

I've put the code for this MailApp up on my GitHub repo https://github.com/gscales/MailApps/tree/master/OWAVoting  if you decide to test it and find any bugs please submit them back. As I mentioned before you should consider this experimental only. All the code is included in the script.js file and uses the workaround I described here as this was only a test app I haven't put any error processing in the Aysnc callbacks.

Exchange EWS eDiscovery Powershell Module

$
0
0
eDiscovery is one of the more useful features introduced in Exchange 2013, and offers a quick and powerful way of Searching and reporting on Items in a Mailbox or across multiple mailboxes on an Exchange Server or in Exchange Online. In this post I wanted to rollup a few eDiscovery scripts I posted in the past to a more user friendly and expandable PowerShell module.

eDiscovery uses KQL (Keyword Query Language) to search indexed properties which are listed on https://technet.microsoft.com/en-us/library/dn774955(v=exchg.150).aspx . For doing quick reporting with eDiscovery you can tell Exchange to only return the number (and size) of the items that match your KQL query. Otherwise Exchange will return preview items (200 at time) which means the query can take some time to complete if your enumerating though a large result set.

I've tried to take a very modular approach with the code in this module to make it easier to extend

Permissions - The eDiscovery parts of this module requires the account that is running the script be a member of the Discovery Search RBAC role see . The code that gets the FolderPath does require the user running the script have rights to the Mailbox or EWS Impersonation rights.

Here are what the cmdlet's in the Module can do at the moment

 Get-MailboxItemStats

This is just a generic cmdlet you can either enter in some KQL to make a query or entering in a Start and End Date to create a report of Items in a particular Date Range eg to show email from the Last month

Get-MailboxItemStats -MailboxName Mailbox@domain.com -Start (Get-Date).AddDays(-31) -End (Get-Date)

this will show results like



If you use the FolderPath switch this will instead show a folderlevel view of the results to get the folder list it must use the previewItems which are slower to retrieve eg

Get-MailboxItemStats -MailboxName Mailbox@domain.com -Start (Get-Date).AddDays(-31) -End (Get-Date) -FolderPath

would yield a results like


 Get-MailboxItemTypeStats

This is from one of my previous posts and returns a list of ItemTypes in a Mailbox, I've added a parameter so you can do a query on just one itemtype as well. So running it like this would yield a report of the Contacts in a Mailbox and where they are located


 Get-MailboxConversationStats

This lets you report on the From,To,CC and BCC fields of a message with Exchange these fields are indexed in the Participants property (as well as there own keywords for Recipients, to etc). Some cool things you can do with this is query Mailbox Traffic to and from a particular domain eg


If you then want to know more about one particular recipient type you can take the value in the Name property and use that in Get-MailboxItemTypeStats eg



Attachments

One of the more useful things that you can do with this module is search and download attachments from a Mailbox using eDiscovery which you can't easily automate in the eDiscovery Console. So I've got a few different options for this. First I have

Get-AttachmentTypeMailboxStats

This works like the ItemType cmdlet in that it makes use of doing multiple OR queries on a list of attachment types. By default if you don't pass in an array of Attachmenttypes to query I have a list of 8 common types so it will produce a report like this



If you want to run a query based on one AttachmentType across folders you can use something like the following


Or limit it to one particular Attachment Name


Get-MailboxAttachments 

You can download attachments using this cmdlet eg



If Exchange online if you have reference Attachments located in One Drive the module does have code to detect and download those using the sharepoint client libraries. You do need to change the version in

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

to

$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2015

I've put the Module up on GitHub https://github.com/gscales/Powershell-Scripts/tree/master/eDiscovery

You can download a copy of the script from here I've include a compile version of the latest version of the Managed API which include the update to process reference attachments.

EWS Create Mailbox folder Powershell module for Exchange and Office365 Mailboxes

$
0
0
This is a rollup post for a couple of scripts I've posted in the past for creating folders using EWS in an Exchange OnPremise or Exchange online Cloud mailbox. It can do the following

  • Create a Folder in the Root of the Mailbox
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test
  • Create a Folder as a SubFolder of the Inbox
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test -ParentFolder '\Inbox'
  • Create a Folder as a SubFolder of the Inbox using EWS Impersonation
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test -ParentFolder '\Inbox' -useImpersonation
  • Create a new Contacts Folder as a SubFolder of the Mailboxes Contacts Folder
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test -ParentFolder '\Contacts' -FolderClass IPF.Contact

  • Create a new Calendar Folder as a SubFolder of the Mailboxes Calendar Folder
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test -ParentFolder '\Calendar' -FolderClass IPF.Appointment

    The script will also detect if a folder with that name currently exists before trying to create a new folder.

I've put this module up on my github repo here you can download a copy here

The code looks like

functionConnect-Exchange{ 
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials
)
Begin
{
Load-EWSManagedAPI

## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_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($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
#Credentials Option 2
#service.UseDefaultCredentials = $true
#$service.TraceEnabled = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates

Handle-SSL

## 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})
Write-host ("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)
if(!$service.URL){
throw "Error connecting to EWS"
}
else
{
return$service
}
}
}

function Load-EWSManagedAPI{
param(
)
Begin
{
## 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
}
}
}

function Handle-SSL{
param(
)
Begin
{
## 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

}
}

functionGet-FolderFromPath{
param (
[Parameter(Position=0, Mandatory=$true)] [string]$FolderPath,
[Parameter(Position=1, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=2, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=3, Mandatory=$false)] [Microsoft.Exchange.WebServices.Data.PropertySet]$PropertySet
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1;$lint-lt$fldArray.Length;$lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
if(![string]::IsNullOrEmpty($PropertySet)){
$fvFolderView.PropertySet = $PropertySet
}
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folderin$findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
Write-host ("Error Folder Not Found check path and try again")
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder-ne$null){
return[Microsoft.Exchange.WebServices.Data.Folder]$tfTargetFolder
}
else{
throw ("Folder Not found")
}
}
}


#######################
<#
.SYNOPSIS
Creates a Folder in a Mailbox using the Exchange Web Services API

.DESCRIPTION
Creates a Folder in a Mailbox using the Exchange Web Services API

Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951

.EXAMPLE
Example 1 To create a Folder named test in the Root of the Mailbox
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test

Example 2 To create a Folder as a SubFolder of the Inbox
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test -ParentFolder '\Inbox'

Example 3 To create a new Folder Contacts SubFolder of the Contacts Folder
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test -ParentFolder '\Contacts' -FolderClass IPF.Contact

Example 4 To create a new Folder using EWS Impersonation
Create-Folder -Mailboxname mailbox@domain.com -NewFolderName test -ParentFolder '\Inbox' -useImpersonation

#>
########################
function Create-Folder{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$true)] [String]$NewFolderName,
[Parameter(Position=3, Mandatory=$false)] [String]$ParentFolder,
[Parameter(Position=4, Mandatory=$false)] [String]$FolderClass,
[Parameter(Position=5, Mandatory=$false)] [switch]$useImpersonation
)
Begin
{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$NewFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service)
$NewFolder.DisplayName = $NewFolderName
if(([string]::IsNullOrEmpty($folderClass))){
$NewFolder.FolderClass = "IPF.Note"
}
else{
$NewFolder.FolderClass = $folderClass
}
$EWSParentFolder = $null
if(([string]::IsNullOrEmpty($ParentFolder))){
# Bind to the MsgFolderRoot folder
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$EWSParentFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
}
else{
$EWSParentFolder = Get-FolderFromPath -MailboxName $MailboxName -service $service -FolderPath $ParentFolder
}
#Define Folder Veiw Really only want to return one object
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
#Define a Search folder that is going to do a search based on the DisplayName of the folder
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$NewFolderName)
#Do the Search
$findFolderResults = $service.FindFolders($EWSParentFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -eq 0){
Write-host ("Folder Doesn't Exist")
$NewFolder.Save($EWSParentFolder.Id)
Write-host ("Folder Created")
}
else{
Write-error ("Folder already Exist with that Name")
}


}
}


Creating and modifying Contact Groups with EWS and Powershell

$
0
0
Mailbox Contact Groups have been around for a while and while there are a number of other Group options in Exchange from a users point of view these are still one of the most efficient ways of creating Distribution groups. I've updated my EWS PowerShell Contacts module to support creating and modifying Contact Groups. So using this module you can now

Create a ContactGroup in the default contacts folder of a Mailbox

Create-ContactGroup -Mailboxname mailbox@domain.com -GroupName GroupName -Members ("member1@domain.com","member2@domain.com")

Add more Members to that Group

$Group = Get-ContactGroup -Mailboxname mailbox@domain.com -GroupName GroupName $Group.Members.Add("newMember@domain.com")
$Group.Update(0)

This was just using the generic Add method but there are a number of methods for adding different types of contacts such as MailEnalbed Public Folders,ContactGroups,Directory Users etc see https://msdn.microsoft.com/en-us/library/office/microsoft.exchange.webservices.data.groupmembercollection_methods%28v=exchg.80%29.aspx

Delete the Group
$Group = Get-ContactGroup -Mailboxname mailbox@domain.com -GroupName GroupName
$Group.Delete(2)

 The Contacts Module is up on GitHub https://github.com/gscales/Powershell-Scripts/tree/master/EWSContacts you can download a zip from here

Setting the Colour of a calendar using EWS and Powershell in Exchange

$
0
0
In Outlook and OWA when you are using the Multiple calendar view feature you can assign different colors to calendars within that view. (By default the calendar color is set to automatic which means a color is assigned at random) eg


When you add a Shared Calendar to your Mailbox you create a Calendar WunderBar Navigational shortcut which are documented https://msdn.microsoft.com/en-us/library/ee202589(v=exchg.80).aspx . The color that these shortcuts will be set to is set via the PidTagWlinkCalendarColor property https://msdn.microsoft.com/en-us/library/ee200641(v=exchg.80).aspx .

So if you want to set the color of one of these shortcuts with a script it does require a little bit of work. First to get the shortcut Items you need to first get the CommonViews folder and then enumerate the FAI Items collection where these shortcuts are stored. Now depending if you want to the Set the Default Folder Shortcut or a Shared Folder Shortcut (or the other calendars Shortcut for Room mailboxes and the like) you need to look at a few different properties. On Shared Calendar Shortcuts you need to look at the PidTagWlinkAddressBookEID property while on the default folder you should look at the PidTagWlinkEntryId property.

I've written a powershell module for doing this and posted it up on GitHub so with this module you can . The CalendarColor enun is mildly accurate

Set the Color of the Default Calendar Folder in a Mailbox

Set-DefaultCalendarFolderShortCut -MailboxName user@domain.com -CalendarColor Yellow

 (To just get the EWS Navigation Item for the default calendar folder use)

Get-DefaultCalendarFolderShortCut -MailboxName user@domain.com

To Set the Color of a Shared Calendar folder use

Set-SharedCalendarFolderShortCut -MailboxName user@datarumble.com -SharedCalendarMailboxName sharedcalendar@domain.com -CalendarColor Yellow

You can download a copy of the module from here the code itself looks like
functionConnect-Exchange{ 
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials
)
Begin
{
Load-EWSManagedAPI

## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_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($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
#Credentials Option 2
#service.UseDefaultCredentials = $true
#$service.TraceEnabled = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates

Handle-SSL

## 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})
Write-host ("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)
if(!$service.URL){
throw "Error connecting to EWS"
}
else
{
return$service
}
}
}

function Load-EWSManagedAPI{
param(
)
Begin
{
## 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
}
}
}

function Handle-SSL{
param(
)
Begin
{
## 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

}
}

functionGet-FolderFromPath{
param (
[Parameter(Position=0, Mandatory=$true)] [string]$FolderPath,
[Parameter(Position=1, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=2, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=3, Mandatory=$false)] [Microsoft.Exchange.WebServices.Data.PropertySet]$PropertySet
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1;$lint-lt$fldArray.Length;$lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
if(![string]::IsNullOrEmpty($PropertySet)){
$fvFolderView.PropertySet = $PropertySet
}
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folderin$findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
Write-host ("Error Folder Not Found check path and try again")
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder-ne$null){
return[Microsoft.Exchange.WebServices.Data.Folder]$tfTargetFolder
}
else{
throw ("Folder Not found")
}
}
}

if (-not ([System.Management.Automation.PSTypeName]'CalendarColor').Type)
{
Add-Type -TypeDefinition @"
public enum CalendarColor
{
Automatch = -1,
Blue = 0,
Green = 1,
Peach = 2,
Gray = 3,
Teal = 4,
Pink = 5,
Olive = 6,
Red = 7,
Orange = 8,
Purple = 9,
Tan = 10,
Light_Green = 12,
Yellow = 13,
Eton_Blue = 14
}
"@
}

functionGet-CommonViews{
param (
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service
)
process{
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"Common Views")
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
return$findFolderResults.Folders[0]
}
else{
return$null
}
}
}


function Exec-FindCalendarShortCut{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service
)
Begin
{
# Bind to the Calendar Folder
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$pidTagEntryId = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0FFF, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkEntryId = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684C, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkGroupName = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6851, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$PidTagWlinkCalendarColor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6853, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$psPropset.Add($pidTagEntryId)
$psPropset.Add($PidTagWlinkGroupName)
$psPropset.Add($PidTagWlinkCalendarColor)
$Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)
$CalendarEntryId = $null
[Void]$Calendar.TryGetProperty($pidTagEntryId,[ref]$CalendarEntryId)
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PidTagWlinkEntryId,[System.Convert]::ToBase64String($CalendarEntryId))
#Get the HexId
$CommonViewFolder = Get-CommonViews -MailboxName $MailboxName -service $service
$retrunItem = @()
if($CommonViewFolder-ne$null){
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$ivItemView.PropertySet = $psPropset
$fiItems = $null
do{
$fiItems = $service.FindItems($CommonViewFolder.Id,$SfSearchFilter,$ivItemView)
#[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Itemin$fiItems.Items){
$retrunItem += $Item
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq$true)
}
return$retrunItem
}
}
#######################
<#
.SYNOPSIS
Gets the Calendar WunderBar ShortCut for the Default Calendar Folder in a Mailbox using the Exchange Web Services API

.DESCRIPTION
Gets the Calendar WunderBar ShortCut for the Default Calendar Folder using the Exchange Web Services API

Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951

.EXAMPLE
Example 1 To create a Folder named test in the Root of the Mailbox
Get-DefaultCalendarFolderShortCut -Mailboxname mailbox@domain.com
#>
########################
functionGet-DefaultCalendarFolderShortCut{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation
)
Begin
{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$PidTagWlinkGroupName = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6851, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$PidTagWlinkCalendarColor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6853, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$ShortCutItems = Exec-FindCalendarShortCut -MailboxName $MailboxName -service $service
Write-Output ("Number of ShortCuts Founds " + $ShortCutItems.Count)
foreach($Itemin$ShortCutItems){
$GroupNameVal = $null
$CalendarColor = $null
[Void]$Item.TryGetProperty($PidTagWlinkCalendarColor,[ref]$CalendarColor)
if($Item.TryGetProperty($PidTagWlinkGroupName,[ref]$GroupNameVal))
{
Write-Host ("Group Name " + $GroupNameVal + " Color " + [CalendarColor]$CalendarColor)
Write-Output$Item
}
}



}
}
#######################
<#
.SYNOPSIS
Sets the Color of the Calendar WunderBar ShortCut for the Default Calendar Folder in a Mailbox using the Exchange Web Services API

.DESCRIPTION
Sets the Color of the Calendar WunderBar ShortCut for the Default Calendar Folder using the Exchange Web Services API

Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951

.EXAMPLE
Example 1 To create a Folder named test in the Root of the Mailbox
Set-DefaultCalendarFolderShortCut -Mailboxname mailbox@domain.com -CalendarColor Red

#>
########################
functionSet-DefaultCalendarFolderShortCut{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$true)] [CalendarColor]$CalendarColor
)
Begin
{
$intVal = $CalendarColor-as[int]
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
$PidTagWlinkGroupName = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6851, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$PidTagWlinkCalendarColor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6853, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$ShortCutItems = Exec-FindCalendarShortCut -MailboxName $MailboxName -service $service
Write-Output ("Number of ShortCuts Founds " + $ShortCutItems.Count)
foreach($Itemin$ShortCutItems){
$GroupNameVal = $null
$CalendarColorValue = $null
[Void]$Item.TryGetProperty($PidTagWlinkCalendarColor,[ref]$CalendarColorValue)
if($Item.TryGetProperty($PidTagWlinkGroupName,[ref]$GroupNameVal))
{
Write-Host ("Group Name " + $GroupNameVal + " Current Color " + [CalendarColor]$CalendarColorValue)
$Item.SetExtendedProperty($PidTagWlinkCalendarColor,$intVal)
$Item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
}
}



}
}
#######################
<#
.SYNOPSIS
Gets the Calendar WunderBar ShortCut for a Shared Calendar Folder in a Mailbox using the Exchange Web Services API

.DESCRIPTION
Gets the Calendar WunderBar ShortCut for a Shared Calendar Folder using the Exchange Web Services API

Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951

.EXAMPLE
Example 1 To create a Folder named test in the Root of the Mailbox
Get-DefaultCalendarFolderShortCut -Mailboxname mailbox@domain.com -$SharedCalendarMailboxName sharedcalender@domain.com
#>
########################
functionGet-SharedCalendarFolderShortCut{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$true)] [string]$SharedCalendarMailboxName
)
Begin
{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials

if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$PidTagWlinkAddressBookEID = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6854,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkFolderType = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684F, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkCalendarColor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6853, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkGroupName = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6851, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$CommonViewFolder = Get-CommonViews -MailboxName $MailboxName -service $service

Write-Host ("Getting Autodiscover Settings Target")
Write-Host ("Getting Autodiscover Settings Mailbox")
$adset = GetAutoDiscoverSettings -adEmailAddress $MailboxName -Credentials $Credentials
$storeID = ""
if($adset-is[Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverResponse]){
Write-Host ("Get StoreId")
$storeID = GetStoreId -AutoDiscoverSettings $adset
}
$adset = $null
$abTargetABEntryId = ""
$adset = GetAutoDiscoverSettings -adEmailAddress $SharedCalendarMailboxName -Credentials $Credentials
if($adset-is[Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverResponse]){
Write-Host ("Get AB Id")
$abTargetABEntryId = GetAddressBookId -AutoDiscoverSettings $adset
$SharedUserDisplayName = $adset.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::UserDisplayName]
}
$ExistingShortCut = $false
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.add($PidTagWlinkAddressBookEID)
$psPropset.add($PidTagWlinkFolderType)
$psPropset.add($PidTagWlinkCalendarColor)
$psPropset.add($PidTagWlinkGroupName)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$ivItemView.PropertySet = $psPropset
$fiItems = $service.FindItems($CommonViewFolder.Id,$ivItemView)
foreach($Itemin$fiItems.Items){
$aeidVal = $null
$CalendarColorValue = $null
$GroupNameVal = $null
[Void]$Item.TryGetProperty($PidTagWlinkCalendarColor,[ref]$CalendarColorValue)
[Void]$Item.TryGetProperty($PidTagWlinkGroupName,[ref]$GroupNameVal)
if($Item.TryGetProperty($PidTagWlinkAddressBookEID,[ref]$aeidVal)){
$fldType = $null
if($Item.TryGetProperty($PidTagWlinkFolderType,[ref]$fldType)){
if([System.BitConverter]::ToString($fldType).Replace("-","") -eq"0278060000000000C000000000000046"){
if([System.BitConverter]::ToString($aeidVal).Replace("-","") -eq$abTargetABEntryId){
Write-Host ("Group Name " + $GroupNameVal + " Current Color " + [CalendarColor]$CalendarColorValue)
Write-Output$Item
}
}
}
}
}



}
}
#######################
<#
.SYNOPSIS
Sets the Color of the Shared Calendar WunderBar ShortCut for the Shared Calendar Folder Shortcut in a Mailbox using the Exchange Web Services API

.DESCRIPTION
Sets the Color of the Shared Calendar WunderBar ShortCut for the Shared Calendar Folder Shortcut in a Mailbox using the Exchange Web Services API

Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951

.EXAMPLE
Example 1 To create a Folder named test in the Root of the Mailbox
Set-DefaultCalendarFolderShortCut -Mailboxname mailbox@domain.com -$SharedCalendarMailboxName sharedcalender@domain.com -CalendarColor Red

#>
########################
functionSet-SharedCalendarFolderShortCut{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$true)] [string]$SharedCalendarMailboxName,
[Parameter(Position=4, Mandatory=$true)] [CalendarColor]$CalendarColor
)
Begin
{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
$intVal = $CalendarColor-as[int]
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$PidTagWlinkAddressBookEID = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6854,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkFolderType = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684F, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkCalendarColor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6853, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkGroupName = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6851, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$CommonViewFolder = Get-CommonViews -MailboxName $MailboxName -service $service

Write-Host ("Getting Autodiscover Settings Target")
Write-Host ("Getting Autodiscover Settings Mailbox")
$adset = GetAutoDiscoverSettings -adEmailAddress $MailboxName -Credentials $Credentials
$storeID = ""
if($adset-is[Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverResponse]){
Write-Host ("Get StoreId")
$storeID = GetStoreId -AutoDiscoverSettings $adset
}
$adset = $null
$abTargetABEntryId = ""
$adset = GetAutoDiscoverSettings -adEmailAddress $SharedCalendarMailboxName -Credentials $Credentials
if($adset-is[Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverResponse]){
Write-Host ("Get AB Id")
$abTargetABEntryId = GetAddressBookId -AutoDiscoverSettings $adset
$SharedUserDisplayName = $adset.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::UserDisplayName]
}
$ExistingShortCut = $false
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.add($PidTagWlinkAddressBookEID)
$psPropset.add($PidTagWlinkFolderType)
$psPropset.add($PidTagWlinkCalendarColor)
$psPropset.add($PidTagWlinkGroupName)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$ivItemView.PropertySet = $psPropset
$fiItems = $service.FindItems($CommonViewFolder.Id,$ivItemView)
foreach($Itemin$fiItems.Items){
$aeidVal = $null
if($Item.TryGetProperty($PidTagWlinkAddressBookEID,[ref]$aeidVal)){
$fldType = $null
$CalendarColorValue = $null
$GroupNameVal = $null
[Void]$Item.TryGetProperty($PidTagWlinkCalendarColor,[ref]$CalendarColorValue)
[Void]$Item.TryGetProperty($PidTagWlinkGroupName,[ref]$GroupNameVal)
if($Item.TryGetProperty($PidTagWlinkFolderType,[ref]$fldType)){
if([System.BitConverter]::ToString($fldType).Replace("-","") -eq"0278060000000000C000000000000046"){
if([System.BitConverter]::ToString($aeidVal).Replace("-","") -eq$abTargetABEntryId){
Write-Host ("Group Name " + $GroupNameVal + " Current Color " + [CalendarColor]$CalendarColorValue)
$Item.SetExtendedProperty($PidTagWlinkCalendarColor,$intVal)
$Item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
}
}
}
}
}



}
}


function GetAutoDiscoverSettings{
param (
$adEmailAddress = "$( throw 'emailaddress is a mandatory Parameter' )",
[System.Management.Automation.PSCredential]$Credentials = "$( throw 'Credentials is a mandatory Parameter' )"
)
process{
$adService = New-Object Microsoft.Exchange.WebServices.AutoDiscover.AutodiscoverService($ExchangeVersion);
$adService.Credentials = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
$adService.EnableScpLookup = $false;
$adService.RedirectionUrlValidationCallback = {$true}
$UserSettings = new-object Microsoft.Exchange.WebServices.Autodiscover.UserSettingName[] 3
$UserSettings[0] = [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::UserDN
$UserSettings[1] = [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::InternalRpcClientServer
$UserSettings[2] = [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::UserDisplayName
$adResponse = $adService.GetUserSettings($adEmailAddress , $UserSettings);
return$adResponse
}
}
function GetAddressBookId{
param (
$AutoDiscoverSettings = "$( throw 'AutoDiscoverSettings is a mandatory Parameter' )"
)
process{
$userdnString = $AutoDiscoverSettings.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::UserDN]
$userdnHexChar = $userdnString.ToCharArray();
foreach ($elementin$userdnHexChar) {$userdnStringHex = $userdnStringHex + [System.String]::Format("{0:X}", [System.Convert]::ToUInt32($element))}
$Provider = "00000000DCA740C8C042101AB4B908002B2FE1820100000000000000"
$userdnStringHex = $Provider + $userdnStringHex + "00"
return$userdnStringHex
}
}
function GetStoreId{
param (
$AutoDiscoverSettings = "$( throw 'AutoDiscoverSettings is a mandatory Parameter' )"
)
process{
$userdnString = $AutoDiscoverSettings.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::UserDN]
$userdnHexChar = $userdnString.ToCharArray();
foreach ($elementin$userdnHexChar) {$userdnStringHex = $userdnStringHex + [System.String]::Format("{0:X}", [System.Convert]::ToUInt32($element))}
$serverNameString = $AutoDiscoverSettings.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::InternalRpcClientServer]
$serverNameHexChar = $serverNameString.ToCharArray();
foreach ($elementin$serverNameHexChar) {$serverNameStringHex = $serverNameStringHex + [System.String]::Format("{0:X}", [System.Convert]::ToUInt32($element))}
$flags = "00000000"
$ProviderUID = "38A1BB1005E5101AA1BB08002B2A56C2"
$versionFlag = "0000"
$DLLFileName = "454D534D44422E444C4C00000000"
$WrappedFlags = "00000000"
$WrappedProviderUID = "1B55FA20AA6611CD9BC800AA002FC45A"
$WrappedType = "0C000000"
$StoredIdStringHex = $flags + $ProviderUID + $versionFlag + $DLLFileName + $WrappedFlags + $WrappedProviderUID + $WrappedType + $serverNameStringHex + "00" + $userdnStringHex + "00"
return$StoredIdStringHex
}
}
function hex2binarray($hexString){
$i = 0
[byte[]]$binarray = @()
while($i-le$hexString.length - 2){
$strHexBit = ($hexString.substring($i,2))
$binarray += [byte]([Convert]::ToInt32($strHexBit,16))
$i = $i + 2
}
return ,$binarray
}
function ConvertId($EWSid){
$aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId
$aiItem.Mailbox = $MailboxName
$aiItem.UniqueId = $EWSid
$aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId;
return$service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::StoreId)
}
function LoadProps()
{

}
function Create-SharedCalendarShortCut
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$SourceMailbox,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$true)] [String]$TargetMailbox,
[Parameter(Position=4, Mandatory=$true)] [CalendarColor]$CalendarColor
)
Begin
{
$intVal = $CalendarColor-as[int]
$service = Connect-Exchange -MailboxName $SourceMailbox -Credentials $Credentials

if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $SourceMailbox)
}
$pidTagStoreEntryId = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(4091, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagNormalizedSubject = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E1D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$PidTagWlinkType = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6849, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkFlags = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684A, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkOrdinal = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684B, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkFolderType = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684F, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkSection = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6852, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkGroupHeaderID = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6842, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkSaveStamp = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6847, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkGroupName = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6851, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)
$PidTagWlinkStoreEntryId = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684E, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkGroupClsid = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6850, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkEntryId = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684C, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkRecordKey = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x684D, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkCalendarColor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6853, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkAddressBookEID = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6854,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$PidTagWlinkROGroupType = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6892,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)
$PidTagWlinkAddressBookStoreEID = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x6891,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$CommonViewFolder = Get-CommonViews -MailboxName $SourceMailbox -service $service
#Get the TargetUsers Calendar
# Bind to the Calendar Folder
$fldPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$fldPropset.Add($pidTagStoreEntryId);
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $TargetMailbox)
}
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$TargetMailbox)
$TargetCalendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$fldPropset)
#Check for existing ShortCut for TargetMailbox
#Get AddressBook Id for TargetUser
Write-Host ("Getting Autodiscover Settings Target")
Write-Host ("Getting Autodiscover Settings Mailbox")
$adset = GetAutoDiscoverSettings -adEmailAddress $SourceMailbox -Credentials $Credentials
$storeID = ""
if($adset-is[Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverResponse]){
Write-Host ("Get StoreId")
$storeID = GetStoreId -AutoDiscoverSettings $adset
}
$adset = $null
$abTargetABEntryId = ""
$adset = GetAutoDiscoverSettings -adEmailAddress $TargetMailbox -Credentials $Credentials
if($adset-is[Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverResponse]){
Write-Host ("Get AB Id")
$abTargetABEntryId = GetAddressBookId -AutoDiscoverSettings $adset
$SharedUserDisplayName = $adset.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::UserDisplayName]
}
$ExistingShortCut = $false
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.add($PidTagWlinkAddressBookEID)
$psPropset.add($PidTagWlinkFolderType)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$ivItemView.PropertySet = $psPropset
$fiItems = $service.FindItems($CommonViewFolder.Id,$ivItemView)
foreach($Itemin$fiItems.Items){
$aeidVal = $null
if($Item.TryGetProperty($PidTagWlinkAddressBookEID,[ref]$aeidVal)){
$fldType = $null
if($Item.TryGetProperty($PidTagWlinkFolderType,[ref]$fldType)){
if([System.BitConverter]::ToString($fldType).Replace("-","") -eq"0278060000000000C000000000000046"){
if([System.BitConverter]::ToString($aeidVal).Replace("-","") -eq$abTargetABEntryId){
$ExistingShortCut = $true
Write-Host"Found existing Shortcut"
###$Item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete)
}
}
}
}
}
if($ExistingShortCut-eq$false){
$objWunderBarLink = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service
$objWunderBarLink.Subject = $SharedUserDisplayName
$objWunderBarLink.ItemClass = "IPM.Microsoft.WunderBar.Link"
$objWunderBarLink.SetExtendedProperty($PidTagWlinkAddressBookEID,(hex2binarray $abTargetABEntryId))
$objWunderBarLink.SetExtendedProperty($PidTagWlinkAddressBookStoreEID,(hex2binarray $storeID))
$objWunderBarLink.SetExtendedProperty($PidTagWlinkCalendarColor,$intVal)
$objWunderBarLink.SetExtendedProperty($PidTagWlinkFlags,0)
$objWunderBarLink.SetExtendedProperty($PidTagWlinkGroupName,"Shared Calendars")
$objWunderBarLink.SetExtendedProperty($PidTagWlinkFolderType,(hex2binarray "0278060000000000C000000000000046"))
$objWunderBarLink.SetExtendedProperty($PidTagWlinkGroupClsid,(hex2binarray "B9F0060000000000C000000000000046"))
$objWunderBarLink.SetExtendedProperty($PidTagWlinkROGroupType,-1)
$objWunderBarLink.SetExtendedProperty($PidTagWlinkSection,3)
$objWunderBarLink.SetExtendedProperty($PidTagWlinkType,2)
$objWunderBarLink.IsAssociated = $true
$objWunderBarLink.Save($CommonViewFolder.Id)
Write-Host ("ShortCut Created for - " + $SharedUserDisplayName)
}

}
}




Unread email ews Powershell Module with reply and forward counting

$
0
0
I've done a few of these Unread / Unused mailbox scripts over the years but this one has a bit of a difference. As well as counting the Total number of unread email in the Inbox over a certain period of time it uses the PidTagLastVerbExecuted property to count how many email messages over that period of time had the client action ReplytoSender, ReplyAll or forwarded and also the number of email in the SentItems folder. This property is set on messages in the Inbox message when one of those actions is taken by the client so it is useful for tracking the use of Mailboxes and gathering statistics around how they are being used. eg here are a few samples of running this module



The code uses both EWS and the Exchange Management Shell to get information about the Mailbox so you need to run it from within the EMS or a Remote PowerShell session (see this if your running it from Office365). I've put the script up on GitHub or you can download it from here. I've also created a Search Filter version of the code, this would work on Exchange 2007 and also if you have an issue where you only see a maximum of 250 items (which is an AQS bug in some version of Exchange) this will address this issue this is also on gihub here


The code itself looks like

functionConnect-Exchange{ 
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [string]$url
)
Begin
{
Load-EWSManagedAPI

## 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($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
#Credentials Option 2
#service.UseDefaultCredentials = $true
#$service.TraceEnabled = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates

Handle-SSL

## 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
if($url){
$uri=[system.URI]$url
$service.Url = $uri
}
else{
$service.AutodiscoverUrl($MailboxName,{$true})
}
Write-host ("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)
if(!$service.URL){
throw "Error connecting to EWS"
}
else
{
return$service
}
}
}

function Load-EWSManagedAPI{
param(
)
Begin
{
## 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
}
}
}

function Handle-SSL{
param(
)
Begin
{
## 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

}
}

function CovertBitValue($String){
$numItempattern = '(?=\().*(?=bytes)'
$matchedItemsNumber = [regex]::matches($String, $numItempattern)
$Mb = [INT64]$matchedItemsNumber[0].Value.Replace("(","").Replace(",","")
return[math]::round($Mb/1048576,0)
}

functionGet-UnReadMessageCount{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$false)] [string]$url,
[Parameter(Position=4, Mandatory=$true)] [Int32]$Months
)
Begin
{
$eval1 = "Last" + $Months + "MonthsTotal"
$eval2 = "Last" + $Months + "MonthsUnread"
$eval3 = "Last" + $Months + "MonthsSent"
$eval4 = "Last" + $Months + "MonthsReplyToSender"
$eval5 = "Last" + $Months + "MonthsReplyToAll"
$eval6 = "Last" + $Months + "MonthForward"
$reply = 0;
$replyall = 0
$forward = 0
$rptObj = "" | select MailboxName,Mailboxsize,LastLogon,LastLogonAccount,$eval1,$eval2,$eval4,$eval5,$eval6,LastMailRecieved,$eval3,LastMailSent
$rptObj.MailboxName = $MailboxName
if($url){
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials -url $url
}
else{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
}
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
$AQSString1 = "System.Message.DateReceived:>" + [system.DateTime]::Now.AddMonths(-$Months).ToString("yyyy-MM-dd")
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems,$MailboxName)
$SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly)
$psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived)
$psPropset.Add([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead)
$PidTagLastVerbExecuted = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x1081,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$psPropset.Add($PidTagLastVerbExecuted)
$ivItemView.PropertySet = $psPropset
$MailboxStats = Get-MailboxStatistics$MailboxName
$ts = CovertBitValue($MailboxStats.TotalItemSize.ToString())
write-host ("Total Size : " + $MailboxStats.TotalItemSize)
$rptObj.MailboxSize = $ts
write-host ("Last Logon Time : " + $MailboxStats.LastLogonTime)
$rptObj.LastLogon = $MailboxStats.LastLogonTime
write-host ("Last Logon Account : " + $MailboxStats.LastLoggedOnUserAccount )
$rptObj.LastLogonAccount = $MailboxStats.LastLoggedOnUserAccount
$fiItems = $null
$unreadCount = 0
$settc = $true
do{
$fiItems = $Inbox.findItems($AQSString1,$ivItemView)
if($settc){
$rptObj.$eval1 = $fiItems.TotalCount
write-host ("Last " + $Months + " Months : " + $fiItems.TotalCount)
if($fiItems.TotalCount -gt 0){
write-host ("Last Mail Recieved : " + $fiItems.Items[0].DateTimeReceived )
$rptObj.LastMailRecieved = $fiItems.Items[0].DateTimeReceived
}
$settc = $false
}
foreach($Itemin$fiItems.Items){
$unReadVal = $null
if($Item.TryGetProperty([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead,[ref]$unReadVal)){
if(!$unReadVal){
$unreadCount++
}
}
$lastVerb = $null
if($Item.TryGetProperty($PidTagLastVerbExecuted,[ref]$lastVerb)){
switch($lastVerb){
102 { $reply++ }
103 { $replyall++}
104 { $forward++}
}
}
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq$true)

write-host ("Last " + $Months + " Months Unread : " + $unreadCount )
$rptObj.$eval2 = $unreadCount
$rptObj.$eval4 = $reply
$rptObj.$eval5 = $replyall
$rptObj.$eval6 = $forward
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)
$fiResults = $SentItems.findItems($AQSString1,$ivItemView)
write-host ("Last " + $Months + " Months Sent : " + $fiResults.TotalCount )
$rptObj.$eval3 = $fiResults.TotalCount
if($fiResults.TotalCount -gt 0){
write-host ("Last Mail Sent Date : " + $fiResults.Items[0].DateTimeSent )
$rptObj.LastMailSent = $fiResults.Items[0].DateTimeSent
}
Write-Output$rptObj
}
}

Introducing the Data Rumble Message Tracking Outlook AddIn for Office 365

$
0
0
With great pride and a little trepidation I'd like to introduce my new venture Data Rumble and our first software release Message Tracking Outllook Addin for Office 365. As a company we are looking to focus on email data so looking at the Metadata an Email message picks up and leaves behind while its in transit to and from a mailbox is the logical first step for us. Launching something new is a lot of work so this first release is a bit of dry run to get all the underlying infrastructure and procedures in place.

At release the main feature of this AddIn is that it allows you to perform a Message Trace on a Message from within Outlook itself using the Office365 Reporting Web Service. This is a REST endpoint that allows you to perform a number of different Office365 Administrative reporting tasks it has been around for a couple of years now and pre dates some of the oauth features that the newer mailbox REST services have so I expect the Endpoint will change soon  (it also has the option to use PowerShell as an alternative to cater for instances where REST doesn't work). . But from this AddIn's perspective it makes use of this endpoint to perform a Message Trace (that you would normally do in the EAC or PowerShell eg https://technet.microsoft.com/EN-US/library/jj200741(v=exchg.150).aspx ) from within Outlook eg. this a screenshot


The Addin post processes the data returned by the Reporting service as well as data extracted from the messages Transport Headers and a few other Message properties and combines those together to provide more information around the message such as the Message header properties, Exchange online protection actions etc (although there is already a good tool for this https://testconnectivity.microsoft.com/ and a corresponding AddIn but this is just another view of the data),Sender and Recipient information. All Message Tracking is done based on the MessageId's extracted from the message in Outlook, it also extracts any associated MessageID's eg if this message is a reply, forward or part of a thread. So you can then query the logs for any associated Messages as well as query for other messages that have been sent or received to and from any of the sender, recipients or envelope recipients found in the log. (envelope recipients would could be BCC's, alternate recipients, forwards or recipients added by a Transport Agent that are available in the Tracing logs). I've created a short video below to showcase the features or you can check and download the software from the product page here.

Using eDiscovery to do a Multi Mailbox search for Mailbox Item Statistics in Exchange

$
0
0
eDiscovery in Exchange 2013 and above has a multitude of uses when it comes to both data discovery and also reporting. One thing you can do with eDiscovery is run a single Query across multiple mailboxes using one request. A couple of month ago I posted this eDiscovery Powershell module on GitHub . This module has a number of cmdlets that does single mailbox queries using eDiscovery so I've created a new cmdlet Search-MultiMailboxesItemStats  for doing Multi Mailbox queries. This allows you to pass in an array of mailboxes you want processed and it will return statistics about how many Items and the Size of those items in bytes based on that query. For example to Query the number of Email received in the last month across a number of mailboxes use

Search-MultiMailboxesItemStats -Mailboxes @('mailbox@domain.com','mailbox2@domain.com')  -QueryString ('Received>' + (Get-Date).AddDays(-31).ToString("yyyy-MM-dd"))

And it will output something like

 
Or if you just want to look at the email that came from a specific domain you could use

Search-MultiMailboxesItemStats -Mailboxes @('mailbox@domain.com','mailbox2@domain.com')  -QueryString ('Received>' + (Get-Date).AddDays(-31).ToString("yyyy-MM-dd") + " AND From:yahoo.com")

another example eg search for the number of contacts in each Mailbox

Search-MultiMailboxesItemStats -Mailboxes @('mailbox@domain.com','mailbox2@domain.com')  -QueryString 'kind:contacts'


The latest version of the module is posted up on GitHub here or you can download a copy from here

Finding the TimeZone being used in a Mailbox using EWS

$
0
0
If you ever have the need to create a number of events in user mailbox's from a central application or Script where the target Mailboxes span across different time zones then finding each target Mailbox's timezone is a critical thing. In this post I'm going to outline a few different methods of getting the TimeZone using EWS.

If you have access to the Exchange Management Shell cmdlet's to find the users configured timezone you can use either the Get-MailboxCalendarConfiguration or Get-MailboxRegionalConfiguration cmdlets which show you the timezone settings of a Mailbox from the WorkHours configuration of that mailbox. The object in the Mailbox that the information is read from/stored in is the IPM.Configuration.WorkHours FAI (Folder Assoicated Items) object in the Calendar Folder of the Mailbox. So if you want to access the TimeZone setting in Mailbox just using EWS you need to Bind to this UserConfiguration object (FAI object) and then parse the TimeZone Settings out of the WorkHours configuration XML document(which is documented in https://msdn.microsoft.com/en-us/library/ee157798(v=exchg.80).aspx) eg in Powershell something like the following should work okay

function GetWorkHoursTimeZone
{
param(
[Parameter(Position=0, Mandatory=$true)] [String]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service
)
Begin
{
# Bind to the Calendar Folder
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)
$Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$sfFolderSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass, "IPM.Configuration.WorkHours")
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$fiItems = $service.FindItems($Calendar.Id,$sfFolderSearchFilter,$ivItemView)
if($fiItems.Items.Count -eq 1)
{
$UsrConfig = [Microsoft.Exchange.WebServices.Data.UserConfiguration]::Bind($service, "WorkHours", $Calendar.Id, [Microsoft.Exchange.WebServices.Data.UserConfigurationProperties]::All)
[XML]$WorkHoursXMLString = [System.Text.Encoding]::UTF8.GetString($UsrConfig.XmlData)
$returnVal = $WorkHoursXMLString.Root.WorkHoursVersion1.TimeZone.Name
write-host ("Parsed TimeZone : " + $returnVal)
Write-Output$returnVal
}
else
{
write-host ("No Workhours Object in Mailbox")
Write-Output$null
}
}
}

 The other place in an Exchange Malbox where TimeZone information is held is in the OWA Configuration. This is set when a user logs onto to OWA the first time and then selects the timezone, the configuration is then stored in another FAI (in a Roaming Dictionary)with an ItemClass of IPM.Configuration.OWA.UserOptions in the NON_IPM root of the mailbox. Eg the following can be used to access this setting using EWS

function GetOWATimeZone{
param(
[Parameter(Position=0, Mandatory=$true)] [String]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service
)
Begin
{
# Bind to the RootFolder Folder
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$sfFolderSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass, "IPM.Configuration.OWA.UserOptions")
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$fiItems = $service.FindItems($RootFolder.Id,$sfFolderSearchFilter,$ivItemView)
if($fiItems.Items.Count -eq 1)
{
$UsrConfig = [Microsoft.Exchange.WebServices.Data.UserConfiguration]::Bind($service, "OWA.UserOptions", $RootFolder.Id, [Microsoft.Exchange.WebServices.Data.UserConfigurationProperties]::All)
if($UsrConfig.Dictionary.ContainsKey("timezone"))
{
$returnVal = $UsrConfig.Dictionary["timezone"]
write-host ("OWA TimeZone : " + $returnVal)
Write-Output$returnVal
}
else
{
write-host ("TimeZone not set")
Write-Output$null
}


}
else
{
write-host ("No Workhours OWAConfig for Mailbox")
Write-Output$null
}
}
}

If you tried both of these locations and still can't find the TimeZone (or your get a conflicting result) the last method you could try is to enumerate the last X number of appointments that where created by the user in the calendar (either a meeting they organized or a non meeting) and then enumerate the timezone used on those objects. The following is a sample of using the Calendar Appointments to work out the timezone of the user (It should return the most commonly used timezone base on the last 150 Calendar items).

function GetTimeZoneFromCalendarEvents{
param(
[Parameter(Position=0, Mandatory=$true)] [String]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service
)
Begin
{
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)
$Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$tzList = [TimeZoneInfo]::GetSystemTimeZones()
$AppointmentStateFlags = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Appointment,0x8217,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$ResponseStatus = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Appointment,0x8218,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(150)
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($AppointmentStateFlags)
$psPropset.Add($ResponseStatus)
$ivItemView.PropertySet = $psPropset
$fiItems = $service.FindItems($Calendar.Id,$ivItemView)
$tzCount = @{}
if($fiItems.Items.Count -gt 0){
foreach($Itemin$fiItems.Items){
$AppointmentStateFlagsVal = $null
[Void]$Item.TryGetProperty($AppointmentStateFlags,[ref]$AppointmentStateFlagsVal)
$ResponseStatusVal = $null
[Void]$Item.TryGetProperty($ResponseStatus,[ref]$ResponseStatusVal)
if($ResponseStatusVal-eq"Organizer"-bor$AppointmentStateFlagsVal-eq 0)
{
if($tzCount.ContainsKey($Item.TimeZone))
{
$tzCount[$Item.TimeZone]++
}
else
{
$tzCount.Add($Item.TimeZone,1)
}
}
}
}
$returnVal = $null
if($tzCount.Count -gt 0){
$fav = ""
$tzCount.GetEnumerator() | sort -Property Value -Descending | foreach-object {
if($fav-eq"")
{
$fav = $_.Key
}
}
foreach($tzin$tzList)
{
if($tz.DisplayName -eq$fav)
{
$returnVal = $tz.Id
}
}
}
Write-Host ("TimeZone From Calendar Appointments : " + $returnVal)
Write-Output$returnVal
}
}

I've included all of these functions in a module that I've posted on GitHub here

Finding RMS Items in a Mailbox using EWS

$
0
0
If you want to search for emails that have been protected using AD Rights Managed Service using EWS there are a few challenges. How RMS works with Email it is documented in the following Exchange Protocol document https://msdn.microsoft.com/en-us/library/cc463909(v=exchg.80).aspx. The important part when it comes to searching is PidNameContentClass property https://msdn.microsoft.com/en-us/library/office/cc839681.aspx which on RMS messages gets set to rpmsg.message. So to search for this Internet Header you can use a SearchFilter like the following to define the ExtendProperty to search folder

$contentclassIh = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::InternetHeaders,"content-class",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($contentclassIh,"rpmsg.message")

Putting it together in a script that searches a Folder in a Mailbox based on a MailboxName and FolderPath This script is on GitHub https://github.com/gscales/Powershell-Scripts/blob/master/GetRMSItems.ps1  (An important point to note while you can find email that has been protected using RMS with EWS you won't be able to read the encrypted contents using EWS).

functionGet-RMSItems{
param
(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$true)] [String]$FolderPath

)
begin
{
$service = connect-exchange -Mailbox $MailboxName -Credentials $Credentials
$Folder = Get-FolderFromPath -FolderPath $FolderPath -MailboxName $MailboxName -service $service
$contentclassIh = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::InternetHeaders,"content-class",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($contentclassIh,"rpmsg.message")
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$fiItems = $null
do{
$fiItems = $service.FindItems($Folder.Id,$sfItemSearchFilter,$ivItemView)
#[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Itemin$fiItems.Items){
Write-Output$Item
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq$true)

}
}

functionGet-FolderFromPath{
param (
[Parameter(Position=0, Mandatory=$true)] [string]$FolderPath,
[Parameter(Position=1, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=2, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=3, Mandatory=$false)] [Microsoft.Exchange.WebServices.Data.PropertySet]$PropertySet
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1;$lint-lt$fldArray.Length;$lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
if(![string]::IsNullOrEmpty($PropertySet)){
$fvFolderView.PropertySet = $PropertySet
}
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folderin$findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
Write-host ("Error Folder Not Found check path and try again")
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder-ne$null){
return[Microsoft.Exchange.WebServices.Data.Folder]$tfTargetFolder
}
else{
throw ("Folder Not found")
}
}
}

function ConvertToString($ipInputString){
$Val1Text = ""
for ($clInt=0;$clInt-lt$ipInputString.length;$clInt++){
$Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))
$clInt++
}
return$Val1Text
}

One last sample that does a statistics report of all RMS items in a Mailbox using a SearchFilter on the AllItems Search Folder (which is created by the Outlook Desktop client) this outputs a report of the RMS Items and the size of those item in each folder that looks like


This script is on GitHub at https://github.com/gscales/Powershell-Scripts/blob/master/GetRMSItems.ps1

function ConvertToString($ipInputString){  
$Val1Text = ""
for ($clInt=0;$clInt-lt$ipInputString.length;$clInt++){
$Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))
$clInt++
}
return$Val1Text
}

functionGet-RMSItems-AllItemSearch{
param
(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials

)
begin
{
$FolderCache = @{}
$service = connect-exchange -Mailbox $MailboxName -Credentials $Credentials
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$MsgRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
GetFolderPaths -FolderCache $FolderCache -service $service -rootFolderId $MsgRoot.Id
$PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;
$sf1 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"2")
$sf2 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"allitems")
$sfSearchFilterCol = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
$sfSearchFilterCol.Add($sf1)
$sfSearchFilterCol.Add($sf2)
$fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilterCol,$fvFolderView)
$fiItems = $null
$RptCollection = @{}
$ItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
if($fiResult.Folders.Count -eq 1)
{
$Folder = $fiResult.Folders[0]
$contentclassIh = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::InternetHeaders,"content-class",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$sfItemSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($contentclassIh,"rpmsg.message")
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$fiItems = $null
do{
$fiItems = $service.FindItems($Folder.Id,$sfItemSearchFilter,$ivItemView)
#[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Itemin$fiItems.Items){
if($FolderCache.ContainsKey($Item.ParentFolderId.UniqueId))
{
if($RptCollection.ContainsKey($FolderCache[$Item.ParentFolderId.UniqueId]))
{
$RptCollection[$FolderCache[$Item.ParentFolderId.UniqueId]].TotalItems++
$RptCollection[$FolderCache[$Item.ParentFolderId.UniqueId]].TotalSize+= $Item.Size
}
else
{
$fldRptobj = "" | Select FolderName,TotalItems,TotalSize
$fldRptobj.FolderName = $FolderCache[$Item.ParentFolderId.UniqueId]
$fldRptobj.TotalItems = 1
$fldRptobj.TotalSize = $Item.Size
$RptCollection.Add($fldRptobj.FolderName,$fldRptobj)
}
}
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq$true)

}
write-output$RptCollection.Values
}


}

function GetFolderPaths
{
param (
[Parameter(Position=0, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.FolderId]$rootFolderId,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=2, Mandatory=$true)] [PSObject]$FolderCache,
[Parameter(Position=3, Mandatory=$false)] [String]$FolderPrefix
)
process{
#Define Extended properties
$PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);
#Define the FolderView used for Export should not be any larger then 1000 folders due to throttling
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
#Deep Transval will ensure all folders in the search path are returned
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
#Add Properties to the Property Set
$psPropertySet.Add($PR_Folder_Path);
$psPropertySet.Add($PR_MESSAGE_SIZE_EXTENDED)
$fvFolderView.PropertySet = $psPropertySet;
#The Search filter will exclude any Search Folders
$sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")
$fiResult = $null
#The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox
do
{
$fiResult = $service.FindFolders($rootFolderId,$sfSearchFilter,$fvFolderView)
foreach($ffFolderin$fiResult.Folders){
#Try to get the FolderPath Value and then covert it to a usable String
$foldpathval = $null
if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref]$foldpathval))
{
$binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)
$hexArr = $binarry | ForEach-Object { $_.ToString("X2") }
$hexString = $hexArr -join ''
$hexString = $hexString.Replace("FEFF", "5C00")
$fpath = ConvertToString($hexString)
}
if($FolderCache.ContainsKey($ffFolder.Id.UniqueId) -eq$false)
{
if ([string]::IsNullOrEmpty($FolderPrefix)){
$FolderCache.Add($ffFolder.Id.UniqueId,($fpath))
}
else
{
$FolderCache.Add($ffFolder.Id.UniqueId,("\" + $FolderPrefix + $fpath))
}
}
}
$fvFolderView.Offset += $fiResult.Folders.Count
}while($fiResult.MoreAvailable -eq$true)
}
}


Showing the Calendar Configuration of a Mailbox or Meeting Room using EWS

$
0
0
When you configure Calendar processing settings in Exchange either using the Exchange Administrator Centre via the Exchange Management Shell (Set-CalendarProcessing) many of these setting get held in a FAI (Folder Associated Item) in the Calendar Folder of the Mailbox in question. In EWS you can access these configuration objects using the UserConfiguration operation and classes.

The Calendar settings are stored in the Roaming Dictionary format to store each of the different Key and Value pairs. For some of the settings like the bookin policy (In-policy meeting requests) and (Out-of-policy meeting requests) these are stored as an array of ExchangeDn's.

I've put together a script cmdlet called Show-CalendarSettings that can dump out the Roaming dictionary setting for a Calendar Configuration object using EWS. And also for the BookIn and RequestIn policy it will loop through each of the ExchangeDn's in the array and try to resolve each of the entries via ResolveName . All the configuration information is written to file and the results of the ResolveName is written along with the result (eg valid if it resolves couldn't resolve if not). eg it produces an output like




  I've put the script on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/DumpCalSettings.ps1 and the script itself look like

functionShow-CalendarSettings
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials,
[Parameter(Position=2, Mandatory=$false)] [switch]$useImpersonation,
[Parameter(Position=3, Mandatory=$false)] [string]$url
)
Begin
{
$service = Connect-Exchange -MailboxName $MailboxName -Credentials $Credentials
if($useImpersonation.IsPresent){
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
}
# Bind to the Calendar Folder
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)
$Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$UsrConfig = [Microsoft.Exchange.WebServices.Data.UserConfiguration]::Bind($service, "Calendar", $Calendar.Id, [Microsoft.Exchange.WebServices.Data.UserConfigurationProperties]::All)
#$UsrConfig.Dictionary
$rptCollection = @()
foreach($cnfObjin$UsrConfig.Dictionary.Key){
$rptObj = "" | Select Property,Value,Valid
$rptObj.Property = $cnfObj
$rptObj.Valid = "Ok"
$rptObj.Value = $UsrConfig.Dictionary[$cnfObj]
$rptCollection +=$rptObj
if($cnfObj-eq"BookInPolicyLegDN")
{
foreach($LegDnin$UsrConfig.Dictionary["BookInPolicyLegDN"])
{
$ncCol = $service.ResolveName($LegDn, [Microsoft.Exchange.WebServices.Data.ResolveNameSearchLocation]::DirectoryOnly, $false);
if($ncCol.Count -gt 0){
#Write-output ("Found " + $ncCol[0].Mailbox.Address)
$rptObj = "" | Select Property,Value,Valid
$rptObj.Property = "BookInPolicyValue"
$rptObj.Value = $ncCol[0].Mailbox.Address
$rptObj.Valid = "Ok"
$rptCollection +=$rptObj
}
else
{
#Write-Output "Couldn't resolve " + $LegDn
$rptObj = "" | Select Property,Value,Valid
$rptObj.Property = "BookInPolicyValue"
$rptObj.Value = $LegDn
$rptObj.Valid = "Couldn't resolve"
$rptCollection +=$rptObj
}

}
}
if($cnfObj-eq"RequestInPolicyLegDN")
{
foreach($LegDnin$UsrConfig.Dictionary["RequestInPolicyLegDN"])
{
$ncCol = $service.ResolveName($LegDn, [Microsoft.Exchange.WebServices.Data.ResolveNameSearchLocation]::DirectoryOnly, $false);
if($ncCol.Count -gt 0){
#Write-output ("Found " + $ncCol[0].Mailbox.Address)
$rptObj = "" | Select Property,Value,Valid
$rptObj.Property = "RequestInPolicyValue"
$rptObj.Value = $ncCol[0].Mailbox.Address
$rptObj.Valid = "Ok"
$rptCollection +=$rptObj
}
else
{
#Write-Output "Couldn't resolve " + $LegDn
$rptObj = "" | Select Property,Value,Valid
$rptObj.Property = "RequestInPolicyValue"
$rptObj.Value = $LegDn
$rptObj.Valid = "Couldn't resolve"
$rptCollection +=$rptObj
}

}
}
}
Write-Output$rptCollection

$rptCollection | Export-Csv -Path ("$MailboxName-CalendarSetting.csv") -NoTypeInformation
}
}

Reporting on Folder RetentionTag Statistics Exchange 2010-16

$
0
0
Mailbox retention tag application and management are one of the more complex administrative tasks on Exchange from 2010 and one task scripting can be a great help with.

With EWS there are two ways you can deal with retrieving the Retention Tags that have been applied to a Folder or Item. In 2013 and 2016,  EWS returns the retention tags applied to a folder or Item as a Strongly typed property which makes things easier to deal with. In Exchange 2010 you need to access the underlying extended properties for Retention tags (this will also work on 2013 and 2016 because the extended properties are the same).

I've posted up a script module on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/ReportTagged.ps1 that can do this using the Extended properties so it will work on Exchange 2010 and up.

Report-TaggedFolders  - Creates a Report of the FolderTag applied to each of the Folders in a Mailbox (if they are tagged). To get all the availbile Personal Tags the Exchange Management Shell Get-RetentionPolicyTag cmdlet is used. The script also then uses the Tag retrieved from the folder to report then number of Items in that folder that have that Tag applied and produces a report like


Report-UntaggedFolder - Creates a Report of the Untagged Folder in a Mailbox with the FolderClass, ItemCount and ItemSize.

Enumerating and Reporting on all Search Folders in a Mailbox or Archive with EWS and Powershell

$
0
0
Search Folders are a feature of Exchange that allows you to have a static search based on a particular criteria  across one or more folders in a Mailbox. They can serve a number of different functions within a Mailbox and are often used to provide some of the backend functionality of new features.

None of the Exchange Management Shell cmdlets gives you  a good view of the SearchFolders in a Mailbox so EWS can be particular useful for this. Eg I've put a cmdlet together that will enumerate all the Search Folders in a Mailbox (or Archive is you interested in that) and produce a report of the folder number of Items and Size of those items that match the Search Folder criteria. It will produce a report such as



I've put a copy of this script up on GitHub here https://github.com/gscales/Powershell-Scripts/blob/master/SearchFoldersEnums.ps1

To Run the script use

Get-SearchFolders -MailboxName Mailbox@domain.com

to Get Searchfolder from an Archive

Get-SearchFolders -MailboxName Mailbox@domain.com -Archive

to use EWS Impersonation

Get-SearchFolders -MailboxName Mailbox@domain.com -useImpersonation
Viewing all 241 articles
Browse latest View live


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