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

Show the cities and Email has tranisted through using the Mail Header, EWS,Powershell and Geolocation (MEC Sample)

$
0
0
This is Sample 2 from the where's wally section of my MEC talk, In this sample we will look at how you can get the Internet Headers of a Message via EWS. Then using RegEx parse the IPAddress's from the MailHeader and then using the Geolocation code I talk about in sample 1

The Internet Headers on a Message eg


tell you what happened to a message on the way to your inbox for example what SMTP mail-servers its transited through. In EWS this information can be accessed via the PR_TRANSPORT_MESSAGE_HEADERS extended property http://msdn.microsoft.com/en-us/library/office/cc815628.aspx


 By using Regex in Powershell (and some other parsing code to clean the code up) you can parse all the IPAddress's you see in the Received headers then using GeoLocation look these up to determine the city that IPAddress is located in, then compile a list of cities that the email transited through. An example report of running this script on the Junk Email Folder of a Mailbox gives us something like


For more information on the Geolocation code I've used see the 1st example

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. $rptCollection = @()  
  5.   
  6. ## Load Managed API dll    
  7. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
  8.     
  9. ## Set Exchange Version    
  10. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  11.     
  12. ## Create Exchange Service Object    
  13. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  14.     
  15. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  16.     
  17. #Credentials Option 1 using UPN for the windows Account    
  18. $psCred = Get-Credential    
  19. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  20. $service.Credentials = $creds        
  21.     
  22. #Credentials Option 2    
  23. #service.UseDefaultCredentials = $true    
  24.     
  25. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  26.     
  27. ## Code From http://poshcode.org/624  
  28. ## Create a compilation environment  
  29. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  30. $Compiler=$Provider.CreateCompiler()  
  31. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  32. $Params.GenerateExecutable=$False  
  33. $Params.GenerateInMemory=$True  
  34. $Params.IncludeDebugInformation=$False  
  35. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  36.   
  37. $TASource=@' 
  38.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  39.     public class TrustAll : System.Net.ICertificatePolicy { 
  40.       public TrustAll() {  
  41.       } 
  42.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  43.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  44.         System.Net.WebRequest req, int problem) { 
  45.         return true; 
  46.       } 
  47.     } 
  48.   } 
  49. '@   
  50. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  51. $TAAssembly=$TAResults.CompiledAssembly  
  52.   
  53. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  54. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  55. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  56.   
  57. ## end code from http://poshcode.org/624  
  58.     
  59. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
  60.     
  61. #CAS URL Option 1 Autodiscover    
  62. $service.AutodiscoverUrl($MailboxName,{$true})    
  63. "Using CAS Server : " + $Service.url     
  64.      
  65. #CAS URL Option 2 Hardcoded    
  66.     
  67. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  68. #$service.Url = $uri      
  69.     
  70. ## Optional section for Exchange Impersonation    
  71.     
  72. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  73.   
  74. # Bind to the Inbox Folder  
  75. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::JunkEmail,$MailboxName)     
  76. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  77.   
  78. #Define Property for the Message Header  
  79. $PR_TRANSPORT_MESSAGE_HEADERS = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x007D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);  
  80.   
  81. $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  82. $psPropset.add($PR_TRANSPORT_MESSAGE_HEADERS)  
  83.   
  84. #Setup GEOIP  
  85. $geoip = New-Object -ComObject "GeoIPCOMEx.GeoIPEx"  
  86. $geoip.set_db_path('c:\mec\') 
  87.  
  88. #Define ItemView to retrive just 10 Items     
  89. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(50)         
  90.     $fiItems = $service.FindItems($Inbox.Id,$ivItemView)     
  91.     [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)   
  92.     foreach($Item in $fiItems.Items){       
  93.         $MailHeaderVal = $null 
  94.         if($Item.TryGetProperty($PR_TRANSPORT_MESSAGE_HEADERS,[ref]$MailHeaderVal)){     
  95.             $rptObj = "" | Select DateTimeReceived,From,Subject,Size,TransitCities 
  96.             $rptObj.DateTimeReceived = $Item.DateTimeReceived  
  97.             $rptObj.From = $Item.Sender.Address 
  98.             if($Item.Subject.Length -gt 50){ 
  99.                 $rptObj.Subject = $Item.Subject.SubString(0,50) 
  100.             } 
  101.             else{ 
  102.                 $rptObj.Subject = $Item.Subject 
  103.             } 
  104.             $rptObj.Size = $Item.Size 
  105.             $SMTPTrace = $MailHeaderVal.Substring(0,$MailHeaderVal.IndexOf("From:")) 
  106.             # RegEx for IP address 
  107.             $RegExIP = '\b(([01]?\d?\d|2[0-4]\d|25[0-5])\.){3}([01]?\d?\d|2[0-4]\d|25[0-5])\b'  
  108.             $matchedItems = [regex]::matches($SMTPTrace$RegExIP)  
  109.             $lochash = @{}  
  110.             foreach($Match in $matchedItems){  
  111.                 $preVal = $MailHeaderVal.SubString(($Match.Index-1),1)  
  112.                 if($preVal -eq "[" -bor $preVal -eq "("){  
  113.                     if($geoip.find_by_addr($Match.Value)){  
  114.                         if($geoip.country_name -ne "Localhost" -band $geoip.country_name -ne "Local Area Network"){  
  115.                             if($lochash.ContainsKey(($geoip.city + "-" +  $geoip.country_name)) -eq $false){  
  116.                                 $lochash.add(($geoip.city + "-" +  $geoip.country_name),1)  
  117.                                 $rptObj.TransitCities = $rptObj.TransitCities + $geoip.city + "-" +  $geoip.country_name + ";"  
  118.                             }  
  119.                         }  
  120.                     }  
  121.                 }  
  122.             }  
  123.             $rptCollection += $rptObj  
  124.         }            
  125.     }  
  126. $tableStyle = @" 
  127. <style> 
  128. BODY{background-color:white;} 
  129. TABLE{border-width: 1px; 
  130.   border-style: solid; 
  131.   border-color: black; 
  132.   border-collapse: collapse; 
  133. } 
  134. TH{border-width: 1px; 
  135.   padding: 10px; 
  136.   border-style: solid; 
  137.   border-color: black; 
  138.   background-color:#66CCCC 
  139. } 
  140. TD{border-width: 1px; 
  141.   padding: 2px; 
  142.   border-style: solid; 
  143.   border-color: black; 
  144.   background-color:white 
  145. } 
  146. </style> 
  147. "@  
  148.     
  149. $body = @" 
  150. <p style="font-size:25px;family:calibri;color:#ff9100">  
  151. $TableHeader  
  152. </p>  
  153. "@  
  154.   
  155. $rptCollection | ConvertTo-HTML -head $tableStyle –body $body | Out-File c:\temp\jnkmhReport.htm  




EWS Snippets for PowerGui

$
0
0
Snippets are an idea that has been around in IDE environments for quite some time and can really be a time saver when it comes to writing EWS scripts. The following are the Snippets for PowerGUI that i presented in my MEC talk in September (Sorry I'm a little late in publishing these).

To use these snippits first you need to have powergui then you can download the Zip file that contains all the snippits from http://msgdev.mvps.org/exdevblog/ews-PowerGuiSnipits.zip . 

Once you have unzipped the files to a directory run the ./installsnippits.ps1 script from this directory which will create a snippits folder under %UserName%\My Documents\WindowsPowerShell and copy the snippits into this directory.

Using the Snippits is pretty easy but you do need to understand a little bit about building EWS scripts. The majority of the EWS scripts you create will have 4 major phases

1st Phase - Is to connect and authenticate to the CAS server, in this day and age you should have a proper SSL certificate and Autodiscover should be configured.  So in this first phase you need to acquire from somewhere the SMTPAddress of the mailbox you want to access and credentials for you to use to access the mailbox. Then perform a Autodiscover using the SMTPAddress and credentials you acquired which should then return the URL for EWS.

I've bundled this phase into the following snippit



The snippit will take the SMTPAddress as a cmdline parameter

$MailboxName = $args[0]

And also grab the credentials from the user (make sure you use the UPN as the username when it prompts)

$psCred = Get-Credential 
$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString()) 
$service.Credentials = $creds     

2nd Phase - Now you have the URL for the CAS server to send your EWS request to, the next step is to bind to the folder you want to work with. For all the WellKnownFolders you can bind using the WellKnownFolder enum. I've bundled this phase together in another snippit for each folder for example to bind to the Inbox you use



If you wanted to instead bind to a Subfolder or user created folder I have another snippit for this phase which will bind to a folder using a path.





3rd Phase - Once you have the Folderid of the Folder you want to work with you can then either work with that folder or work with the Items in that folder so the snippit to use if you wish to Enumerate All Folder Items is (there are also several example of searches)




 With this snippit you need to replace the $Folder.Id with the Folder.Id from the other Snippit eg if you used

$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)  
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

You should then change

$fiItems = $service.FindItems($Folder.Id,$ivItemView)

to

$fiItems = $service.FindItems($Inbox.Id,$ivItemView)

4th Phase : There is no snippit for this but its basically where you put the code to process the Items eg you could output the Subject

    foreach($Item in $fiItems.Items){     
                $Item.Subject    
    }

or any of the other things I've listed in my other Howto http://gsexdev.blogspot.com.au/2012/02/ews-managed-api-and-powershell-how-to.html


That's it there are over 60 snippits altogether that I'll try to grow over time if you have any you want to include to help others out please send them to me and I'll include them.

Creating a sender domain auto reply rule for a mailbox with EWS and Powershell

$
0
0
This is a rewrite of an old CDO 1.2 rule.dll script back from 2006 http://gsexdev.blogspot.com.au/2006/10/creating-domain-based-auto-response.html .

Creating a Domain based auto response rule allows you to have a custom auto-responder for an email based on the sender's email domain of any newly received email messages. This is useful when you need to cater for a number of different scenarios and where you want to add a more personal touch to these auto responders. 

EWS allows you to create Inbox rules via the CreateRule Operation if your after the full spiel have a read of http://msdn.microsoft.com/en-us/library/exchange/hh298418%28v=exchg.140%29.aspx

In this script it first uses a ContainsSenderStrings condition to filter on the domain you want the rule to apply to in my example yahoo.com

$nrNewInboxRule.Conditions.ContainsSenderStrings.Add("@yahoo.com")

Exceptions are created for any messages with subject prefixes of RE and FW to omit these messages from an Auto-Response.

$nrNewInboxRule.Exceptions.ContainsSubjectStrings.Add("RE:");
$nrNewInboxRule.Exceptions.ContainsSubjectStrings.Add("FW:")

The script also creates the auto response message which is a message that is saved in the Assoicated Items collection of the Inbox with a message class of "IPM.Note.Rules.ReplyTemplate.Microsoft". One important thing is to set the PidTagReplyTemplateId property on the Template messages as per http://msdn.microsoft.com/en-us/library/ff367988%28v=EXCHG.80%29.aspx 

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.   
  3. $MailboxName = $args[0]  
  4.   
  5. ## Load Managed API dll    
  6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
  7.     
  8. ## Set Exchange Version    
  9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
  10.     
  11. ## Create Exchange Service Object    
  12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
  13.     
  14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
  15.     
  16. #Credentials Option 1 using UPN for the windows Account    
  17. $psCred = Get-Credential    
  18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
  19. $service.Credentials = $creds        
  20.     
  21. #Credentials Option 2    
  22. #service.UseDefaultCredentials = $true    
  23.     
  24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  25.     
  26. ## Code From http://poshcode.org/624  
  27. ## Create a compilation environment  
  28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  29. $Compiler=$Provider.CreateCompiler()  
  30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  31. $Params.GenerateExecutable=$False  
  32. $Params.GenerateInMemory=$True  
  33. $Params.IncludeDebugInformation=$False  
  34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  35.   
  36. $TASource=@' 
  37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  38.     public class TrustAll : System.Net.ICertificatePolicy { 
  39.       public TrustAll() {  
  40.       } 
  41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  43.         System.Net.WebRequest req, int problem) { 
  44.         return true; 
  45.       } 
  46.     } 
  47.   } 
  48. '@   
  49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  50. $TAAssembly=$TAResults.CompiledAssembly  
  51.   
  52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  55.   
  56. ## end code from http://poshcode.org/624  
  57.     
  58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
  59.     
  60. #CAS URL Option 1 Autodiscover    
  61. $service.AutodiscoverUrl($MailboxName,{$true})    
  62. "Using CAS Server : " + $Service.url     
  63.      
  64. #CAS URL Option 2 Hardcoded    
  65.     
  66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
  67. #$service.Url = $uri      
  68.     
  69. ## Optional section for Exchange Impersonation    
  70.     
  71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
  72.   
  73. $tmTemplateEmail = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service  
  74. $tmTemplateEmail.ItemClass = "IPM.Note.Rules.ReplyTemplate.Microsoft";  
  75. $tmTemplateEmail.IsAssociated = $true;  
  76. $tmTemplateEmail.Subject = "Recipient of your Email action required";  
  77. $htmlBodyString = "Hello,<p>Thanks for your Email we only answer emails enqiures from coperates email domains";  
  78. $tmTemplateEmail.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody($htmlBodyString);  
  79. $PidTagReplyTemplateId = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x65C2, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)  
  80. $tmTemplateEmail.SetExtendedProperty($PidTagReplyTemplateId, [System.Guid]::NewGuid().ToByteArray());  
  81. $tmTemplateEmail.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox);  
  82. $nrNewInboxRule = New-Object Microsoft.Exchange.WebServices.Data.Rule   
  83. $nrNewInboxRule.DisplayName = "Auto Reply Rule";  
  84. $nrNewInboxRule.Conditions.ContainsSenderStrings.Add("@yahoo.com")  
  85. $nrNewInboxRule.Actions.ServerReplyWithMessage = $tmTemplateEmail.Id;  
  86. $nrNewInboxRule.Exceptions.ContainsSubjectStrings.Add("RE:");  
  87. $nrNewInboxRule.Exceptions.ContainsSubjectStrings.Add("FW:");  
  88. $cnCreateNewRule = New-Object Microsoft.Exchange.WebServices.Data.createRuleOperation[] 1  
  89. $cnCreateNewRule[0] = $nrNewInboxRule  
  90. $service.UpdateInboxRules($cnCreateNewRule,$true);

Using eDiscovery to search mailboxes using EWS in Exchange 2013 (the new Exchange)

$
0
0
eDiscovery is one of the new features in Exchange 2013 that has been built on the top of new operations added in Exchange Web Services in 2013. While its very useful for ITPro's for handling legal discoveries and compliance issues, it opens up a whole new world for application developers building Apps that search Exchange Mailboxes. Like the AQS querystring that was introduced in Exchange 2010 the eDiscovery operations make use of the Exchange Search service's content indexes.

There isn't much documentation around at the moment about how to use eDiscovery in EWS so this is just a dump of what I've learned so far.

All up there are four new EWS operations for eDiscovery however the two that you will use most of the time would be.



GetSearchableMailboxes
Gets a list of mailboxes that the client has permission to search or perform eDiscovery on.
SearchMailboxes
Searches for items in specific mailboxes that match query keywords.

from http://msdn.microsoft.com/en-us/library/exchange/jj190897.aspx

Permissions wise you need to be a member of the Discovery Management RBAC role

The eDiscovery operations make use of the Keyword Query Language (KQL) which there is some decent documentation for in the Protocol documents for KQL http://msdn.microsoft.com/en-us/library/hh644280%28v=office.12%29.aspx although this isn't very Exchange specific. KQL is pretty cool and gives greater control and functionality over the queries you make when compared against AQS. The syntax does bare similarities to AQS and in some cases your AQS queries should just be transferable.

Some KQL samples would be

Subject:"New Time Proposed: test"  -  This would do an exact match on the Subject

Subject:Football    - This would do a SubString type query on the Subject

attachment:'.pdf'  - The would search for attachment with a pdf attachment

To use the new eDisocvery operations in the EWS Managed API you need to have v2.0 which was released recently http://www.microsoft.com/en-au/download/details.aspx?id=35371 .

So now it comes to using the SearchMaiboxes operation, to do this you need to have a properly formated KQL query and you need to know the Mailbox Identifier to use. The Mailbox identifier was a bit of tricky one where in most other things in EWS this would be the PrimarySMTPAddress, for this operation its the X500 (or legacyExchangedn) of the mailbox. There are a few ways you could get this the eaiest and most reliable would be to use the GetSearchableMailboxes operation to build the MailboxSearchScope array for the mailboxes you want to search . eg

  1. GetSearchableMailboxesResponse gsMBResponse = service.GetSearchableMailboxes("fsmith"false);  
  2. MailboxSearchScope[] msbScope = new MailboxSearchScope[gsMBResponse.SearchableMailboxes.Length];  
  3. Int32 mbCount = 0;  
  4. foreach (SearchableMailbox sbMailbox in gsMBResponse.SearchableMailboxes) {  
  5.     msbScope[mbCount] = new MailboxSearchScope(sbMailbox.ReferenceId, MailboxSearchLocation.All);  
  6.     mbCount++;  
  7. }  
The Important point is the ReferenceId (which is the X500 address) is the Id that SearchMailbox seems to want.
    Next you build the actually request

    1. SearchMailboxesParameters smSearchMailbox = new SearchMailboxesParameters();  
    2. MailboxQuery mbq = new MailboxQuery("attachment:'.pdf'", msbScope);  
    3. MailboxQuery[] mbqa = new MailboxQuery[1] { mbq };  
    4. smSearchMailbox.SearchQueries = mbqa;  
    5. smSearchMailbox.PageSize = 1000;  
    6. smSearchMailbox.ResultType = Microsoft.Exchange.WebServices.Data.SearchResultType.PreviewOnly;  
    7. service.TraceEnabled = true;  
    8. ServiceResponseCollection<SearchMailboxesResponse> srCol =  service.SearchMailboxes(smSearchMailbox);  
    There are a bunch of options for manipulating the results such as deduplication and the ability to page and sort the results in different ways and orders. You can also choose just to return the statistics of the query without the results, or return the resutls as PreviewItems where you can control what properties are returned in the PreviewItem.

    So for me its a big thumbs up for eDiscovery, so far anyway I can see some frustrations in the way the PriviewItems are returned and there needs to be a lot better info for using KQL and the discovery options.

    Create an RSS feed of Exchange Items using any of the How To Series scripts

    $
    0
    0
    If you've missed my HowTo Series this year I've been demonstrating many different way's of getting, filtering and searching for Exchange data using Powershell and the EWS Managed API see the full list here . One of the fun things to do with scripting is to link this together with the other plethora of script's out in the world to do different things. Here one example of linking the How-To series script with an RSS feed script from the PoshCode repository http://poshcode.org/?show=669

    The following script grabs the last 50 Items in the Inbox and creates an RSS feed of theses Item including Links to OWA for the Item and the Item's Body as HTML in the Feed details.

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

    1. ###Code from http://poshcode.org/?show=669  
    2. # Creates an RSS feed  
    3. # Parameter input is for "site": Path, Title, Url, Description  
    4. # Pipeline input is for feed items: hashtable with Title, Link, Author, Description, and pubDate keys  
    5. Function New-RssFeed  
    6. {   
    7.     param (  
    8.             $Path = "$( throw 'Path is a mandatory parameter.' )",  
    9.             $Title = "Site Title",  
    10.             $Url = "http://$( $env:computername )",  
    11.             $Description = "Description of site"  
    12.     )  
    13.     Begin {  
    14.             # feed metadata  
    15.             $encoding = [System.Text.Encoding]::UTF8  
    16.             $writer = New-Object System.Xml.XmlTextWriter( $Path$encoding )  
    17.             $writer.WriteStartDocument()  
    18.             $writer.WriteStartElement( "rss" )  
    19.             $writer.WriteAttributeString( "version""2.0" )  
    20.             $writer.WriteStartElement( "channel" )  
    21.             $writer.WriteElementString( "title"$Title )  
    22.             $writer.WriteElementString( "link"$Url )  
    23.             $writer.WriteElementString( "description"$Description )  
    24.     }  
    25.     Process {  
    26.             # Construct feed items  
    27.             $writer.WriteStartElement( "item" )  
    28.             $writer.WriteElementString( "title",    $_.title )  
    29.             $writer.WriteElementString( "link",             $_.link )  
    30.             $writer.WriteElementString( "author",   $_.author )  
    31.             $writer.WriteStartElement( "description" )  
    32.             $writer.WriteRaw( "<![CDATA[" ) # desc can contain HTML, so its escaped using SGML escape code  
    33.             $writer.WriteRaw( $_.description )  
    34.             $writer.WriteRaw( "]]>" )  
    35.             $writer.WriteEndElement()  
    36.             $writer.WriteElementString( "pubDate"$_.pubDate.toString( 'r' ) )  
    37.             $writer.WriteElementString( "guid"$homePageUrl + "/" + [guid]::NewGuid() )  
    38.             $writer.WriteEndElement()  
    39.     }  
    40.     End {  
    41.             $writer.WriteEndElement()  
    42.             $writer.WriteEndElement()  
    43.             $writer.WriteEndDocument()  
    44.             $writer.Close()  
    45.     }  
    46. }  
    47. ### end code from http://poshcode.org/?show=669  
    48.   
    49. ## Get the Mailbox to Access from the 1st commandline argument  
    50.   
    51. $MailboxName = $args[0]  
    52.   
    53. ## Load Managed API dll    
    54. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
    55.     
    56. ## Set Exchange Version    
    57. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1    
    58.     
    59. ## Create Exchange Service Object    
    60. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
    61.     
    62. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
    63.     
    64. #Credentials Option 1 using UPN for the windows Account    
    65. $psCred = Get-Credential    
    66. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
    67. $service.Credentials = $creds        
    68.     
    69. #Credentials Option 2    
    70. #service.UseDefaultCredentials = $true    
    71.     
    72. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
    73.     
    74. ## Code From http://poshcode.org/624  
    75. ## Create a compilation environment  
    76. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
    77. $Compiler=$Provider.CreateCompiler()  
    78. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
    79. $Params.GenerateExecutable=$False  
    80. $Params.GenerateInMemory=$True  
    81. $Params.IncludeDebugInformation=$False  
    82. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
    83.   
    84. $TASource=@' 
    85.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
    86.     public class TrustAll : System.Net.ICertificatePolicy { 
    87.       public TrustAll() {  
    88.       } 
    89.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
    90.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
    91.         System.Net.WebRequest req, int problem) { 
    92.         return true; 
    93.       } 
    94.     } 
    95.   } 
    96. '@   
    97. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
    98. $TAAssembly=$TAResults.CompiledAssembly  
    99.   
    100. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
    101. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
    102. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
    103.   
    104. ## end code from http://poshcode.org/624  
    105.     
    106. ## 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    
    107.     
    108. #CAS URL Option 1 Autodiscover    
    109. $service.AutodiscoverUrl($MailboxName,{$true})    
    110. "Using CAS Server : " + $Service.url     
    111.      
    112. #CAS URL Option 2 Hardcoded    
    113.     
    114. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
    115. #$service.Url = $uri      
    116.     
    117. ## Optional section for Exchange Impersonation    
    118.     
    119. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
    120.   
    121. # Bind to the Contacts Folder  
    122.   
    123. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
    124. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
    125. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
    126. #Define ItemView to retrive just 50 Items      
    127. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(50)      
    128. $fiItems = $service.FindItems($Inbox.Id,$ivItemView)      
    129. [Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
    130. $feedItems = @()  
    131. foreach($Item in $fiItems.Items){      
    132.      "processing Item " + $Item.Subject  
    133.      $PostItem = @{}  
    134.      $PostItem.Add("Title",$Item.Subject)  
    135.      $PostItem.Add("Author",$Item.Sender.Address)  
    136.      $PostItem.Add("pubDate",$Item.DateTimeReceived)  
    137.      $PostItem.Add("link",("https://" + $service.Url.Host +  "/owa/" + $Item.WebClientReadFormQueryString))  
    138.      $PostItem.Add("description",$Item.Body.Text)  
    139.      $feedItems += $PostItem         
    140. }      
    141. $feedItems | New-RssFeed -path "c:\temp\Inboxfeed.xml" -title ($MailboxName + " Inbox")  


    Multi Tabbed FreeBusy/OOF Board

    $
    0
    0
    In the past I've posted scripts for a Free-Busy Board and an OOF Board which uses the EWS Managed API and PowerShell. The following is a combination of the two that displays the OOFStatus of the user combined with their FreeBusy and Calendar Appointments in a Tabbed based output eg


    To build the list of Mailboxes to create the Tabs the script uses a Distribution list and the ExpandDL operation which will retrieve a collection of SMTP address's for the members of the group. It then uses the GetUserAvailblity operation and MailTips operation to build the output.

    To run the script you need to use the SMTPAddress of mailbox you want the script to run as and the SMTPAddress of the Distribution list to expand. eg

    fbooftabs.ps1 user@domain.com dl@domain.com

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

    1. $tbRptSourceHeader=@' 
    2. <!DOCTYPE html> 
    3. <html lang="en"> 
    4. <head> 
    5. <style> 
    6. body 
    7. { 
    8.     font-family: "Segoe UI", arial, helvetica, freesans, sans-serif; 
    9.     font-size: 90%; 
    10.     color: #333; 
    11.     background-color: #e5eaff; 
    12.     margin: 10px; 
    13.     z-index: 0; 
    14. } 
    15.  
    16. h1 
    17. { 
    18.     font-size: 1.5em; 
    19.     font-weight: normal; 
    20.     margin: 0; 
    21. } 
    22.  
    23. h2 
    24. { 
    25.     font-size: 1.3em; 
    26.     font-weight: normal; 
    27.     margin: 2em 0 0 0; 
    28. } 
    29.  
    30. p 
    31. { 
    32.     margin: 0.6em 0; 
    33. } 
    34.  
    35. p.tabnav 
    36. { 
    37.     font-size: 1.1em; 
    38.     text-transform: uppercase; 
    39.     text-align: right; 
    40. } 
    41.  
    42. p.tabnav a 
    43. { 
    44.     text-decoration: none; 
    45.     color: #999; 
    46. } 
    47.  
    48. article.tabs 
    49. { 
    50.     position: relative; 
    51.     display: block; 
    52.     width: 80em; 
    53.     height: 30em; 
    54.     margin: 2em auto; 
    55. } 
    56.  
    57. article.tabs section 
    58. { 
    59.     position: absolute; 
    60.     display: block; 
    61.     top: 1.8em; 
    62.     left: 0; 
    63.     height: 42em; 
    64.     padding: 10px 20px; 
    65.     background-color: #ddd; 
    66.     border-radius: 5px; 
    67.     box-shadow: 0 3px 3px rgba(0,0,0,0.1); 
    68.     z-index: 0; 
    69. } 
    70.  
    71. article.tabs section:first-child 
    72. { 
    73.     z-index: 1; 
    74. } 
    75.  
    76. article.tabs section h2 
    77. { 
    78.     position: absolute; 
    79.     font-size: 1em; 
    80.     font-weight: normal; 
    81.     width: 120px; 
    82.     height: 1.8em; 
    83.     top: -1.8em; 
    84.     left: 10px; 
    85.     padding: 0; 
    86.     margin: 0; 
    87.     color: #999; 
    88.     background-color: #ddd; 
    89.     border-radius: 5px 5px 0 0; 
    90. } 
    91. '@   
    92.   
    93. $styleFooter=@' 
    94. article.tabs section h2 a 
    95. { 
    96.     display: block; 
    97.     width: 100%; 
    98.     line-height: 1.8em; 
    99.     text-align: center; 
    100.     text-decoration: none; 
    101.     color: inherit; 
    102.     outline: 0 none; 
    103. } 
    104.  
    105. article.tabs section, 
    106. article.tabs section h2 
    107. { 
    108.     -webkit-transition: all 500ms ease; 
    109.     -moz-transition: all 500ms ease; 
    110.     -ms-transition: all 500ms ease; 
    111.     -o-transition: all 500ms ease; 
    112.     transition: all 500ms ease; 
    113. } 
    114.  
    115. article.tabs section:target, 
    116. article.tabs section:target h2 
    117. { 
    118.     color: #333; 
    119.     background-color: #fff; 
    120.     z-index: 2; 
    121. } 
    122. </Style> 
    123. <meta charset="UTF-8" /> 
    124. <title>Tabbed FreeBusy-Out of Office Board</title> 
    125. <!--[if lt IE 9]> 
    126. <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> 
    127. <![endif]--> 
    128. </head> 
    129. <body> 
    130. <article class="tabs"> 
    131. '@  
    132. ## Get the Mailbox to Access from the 1st commandline argument  
    133.   
    134. $MailboxName = $args[0]  
    135.   
    136. ## Load Managed API dll    
    137. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
    138.     
    139. ## Set Exchange Version    
    140. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
    141.     
    142. ## Create Exchange Service Object    
    143. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
    144.     
    145. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
    146.     
    147. #Credentials Option 1 using UPN for the windows Account    
    148. $psCred = Get-Credential    
    149. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
    150. $service.Credentials = $creds        
    151.     
    152. #Credentials Option 2    
    153. #service.UseDefaultCredentials = $true    
    154.     
    155. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
    156.     
    157. ## Code From http://poshcode.org/624  
    158. ## Create a compilation environment  
    159. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
    160. $Compiler=$Provider.CreateCompiler()  
    161. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
    162. $Params.GenerateExecutable=$False  
    163. $Params.GenerateInMemory=$True  
    164. $Params.IncludeDebugInformation=$False  
    165. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
    166.   
    167. $TASource=@' 
    168.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
    169.     public class TrustAll : System.Net.ICertificatePolicy { 
    170.       public TrustAll() {  
    171.       } 
    172.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
    173.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
    174.         System.Net.WebRequest req, int problem) { 
    175.         return true; 
    176.       } 
    177.     } 
    178.   } 
    179. '@   
    180. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
    181. $TAAssembly=$TAResults.CompiledAssembly  
    182.   
    183. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
    184. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
    185. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
    186.   
    187. ## end code from http://poshcode.org/624  
    188.     
    189. ## 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    
    190.     
    191. #CAS URL Option 1 Autodiscover    
    192. $service.AutodiscoverUrl($MailboxName,{$true})    
    193. "Using CAS Server : " + $Service.url     
    194.      
    195. #CAS URL Option 2 Hardcoded    
    196.     
    197. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
    198. #$service.Url = $uri      
    199.     
    200. ## Optional section for Exchange Impersonation    
    201.     
    202. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
    203.   
    204.   
    205.   
    206. $fbGroup = $service.ExpandGroup($args[1]);  
    207. $StartTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 0:00"))  
    208. $EndTime = $StartTime.AddDays(1)  
    209.   
    210. $displayStartTime =  [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 08:30"))  
    211. $tmValHash = @{ }  
    212. $tidx = 0  
    213.   
    214. for($vsStartTime=[DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 0:00"));$vsStartTime -lt [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 0:00")).AddDays(1);$vsStartTime = $vsStartTime.AddMinutes(30)){  
    215.     $tmValHash.add($vsStartTime.ToString("HH:mm"),$tidx)      
    216.     $tidx++  
    217. }  
    218.     
    219. $drDuration = new-object Microsoft.Exchange.WebServices.Data.TimeWindow($StartTime,$EndTime)    
    220. $AvailabilityOptions = new-object Microsoft.Exchange.WebServices.Data.AvailabilityOptions    
    221. $AvailabilityOptions.RequestedFreeBusyView = [Microsoft.Exchange.WebServices.Data.FreeBusyViewType]::DetailedMerged    
    222.    
    223. $type = ("System.Collections.Generic.List"+'`'+"1") -as "Type"  
    224. $type = $type.MakeGenericType("Microsoft.Exchange.WebServices.Data.AttendeeInfo" -as "Type")  
    225. $Attendeesbatch = [Activator]::CreateInstance($type)   
    226. $mbrRequest = ""  
    227. foreach ($mbr in $fbGroup.Members){  
    228.     $Attendee = new-object Microsoft.Exchange.WebServices.Data.AttendeeInfo($mbr.Address)  
    229.     $mbrRequest = $mbrRequest + "<Mailbox xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`"><EmailAddress>" + $mbr.Address + "</EmailAddress></Mailbox>"   
    230.     $Attendeesbatch.add($Attendee)    
    231. }  
    232.   
    233. $attendeeOOFHash = @{}  
    234. ##Get OOF Status  
    235. $expHeader = @" 
    236. <?xml version="1.0" encoding="utf-8"?> 
    237. <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"> 
    238. <soap:Header><RequestServerVersion Version="Exchange2010_SP1" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" /> 
    239. </soap:Header> 
    240. <soap:Body> 
    241. <GetMailTips xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"> 
    242. <SendingAs> 
    243. <EmailAddress xmlns="http://schemas.microsoft.com/exchange/services/2006/types">$MailboxName</EmailAddress> 
    244. </SendingAs> 
    245. <Recipients> 
    246. "@      
    247.       
    248.   
    249. $expRequest = $expHeader + $mbrRequest + "</Recipients><MailTipsRequested>OutOfOfficeMessage</MailTipsRequested></GetMailTips></soap:Body></soap:Envelope>"  
    250.   
    251. $mbMailboxFolderURI = New-Object System.Uri($service.url)  
    252. $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)  
    253. $wrWebRequest.CookieContainer =  New-Object System.Net.CookieContainer   
    254. $wrWebRequest.KeepAlive = $false;  
    255. $wrWebRequest.Headers.Set("Pragma""no-cache");  
    256. $wrWebRequest.Headers.Set("Translate""f");  
    257. $wrWebRequest.Headers.Set("Depth""0");  
    258. $wrWebRequest.ContentType = "text/xml";  
    259. $wrWebRequest.ContentLength = $expRequest.Length;  
    260. $wrWebRequest.Timeout = 60000;  
    261. $wrWebRequest.Method = "POST";  
    262. $wrWebRequest.Credentials = $creds  
    263. $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($expRequest);  
    264. $wrWebRequest.ContentLength = $bqByteQuery.Length;  
    265. $rsRequestStream = $wrWebRequest.GetRequestStream();  
    266. $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);  
    267. $rsRequestStream.Close();  
    268. $wrWebResponse = $wrWebRequest.GetResponse();  
    269. $rsResponseStream = $wrWebResponse.GetResponseStream()  
    270. $sr = new-object System.IO.StreamReader($rsResponseStream);  
    271. $rdResponseDocument = New-Object System.Xml.XmlDocument  
    272. $rdResponseDocument.LoadXml($sr.ReadToEnd());  
    273. $RecipientNodes = @($rdResponseDocument.getElementsByTagName("t:RecipientAddress"))  
    274. $Datanodes = @($rdResponseDocument.getElementsByTagName("t:OutOfOffice"))  
    275. for($ic=0;$ic -lt $RecipientNodes.length;$ic++){  
    276.     if($Datanodes[$ic].ReplyBody.Message -eq ""){  
    277.         $attendeeOOFHash.add($Attendeesbatch[$ic].SmtpAddress,"In the Office")  
    278.     }  
    279.     else{  
    280.         $attendeeOOFHash.add($Attendeesbatch[$ic].SmtpAddress,"Out of the Office")  
    281.     }  
    282. }  
    283. ### End OOF  
    284.   
    285. $rptOutput = $tbRptSourceHeader  
    286.   
    287. $tabNumber = 1  
    288. $taboffset = 10  
    289. $SectionReport = ""  
    290.   
    291. $atndCnt = 0    
    292. $fbType = [Microsoft.Exchange.WebServices.Data.AvailabilityData]::FreeBusy  
    293. $availresponse = $service.GetUserAvailability($Attendeesbatch,$drDuration,$fbType,$AvailabilityOptions)    
    294. foreach($avail in $availresponse.AttendeesAvailability){    
    295.     if($tabNumber -eq 1){  
    296.           
    297.     }  
    298.     else{  
    299.         $taboffset+=122  
    300.         $rptOutput = $rptOutput + "article.tabs section:nth-child(" + $tabNumber +  ") h2 {     left: " + ($taboffset) + "px;}`r`n"   
    301.           
    302.     }  
    303.     $SectionReport = $SectionReport + "<section id=`"tab" + $tabNumber + "`">`r`n"  
    304.     $SectionReport = $SectionReport + "<h2><a href=`"#tab" + $tabNumber + "`">" + $Attendeesbatch[$atndCnt].SmtpAddress.SubString(0,10) + "</a></h2>`r`n"  
    305.     $SectionReport = $SectionReport + "<p>User : " + $Attendeesbatch[$atndCnt].SmtpAddress + "_________________________________________________________________________________________</p>`r`n"  
    306.     $SectionReport = $SectionReport + "<p>OOF Status : " + $attendeeOOFHash[$Attendeesbatch[$atndCnt].SmtpAddress] + "</p>`r`n"  
    307.     $SectionReport = $SectionReport + "<p>Number of Calendar Events : " + $avail.CalendarEvents.Count + "</p>`r`n"  
    308.     $SectionReport = $SectionReport + "<table><tr>" +"`r`n"  
    309.     $SectionReport = $SectionReport + "<td align=`"center`" style=`"width=200;`" ><b>Time</b></td>" +"`r`n"  
    310.     $SectionReport = $SectionReport + "<td align=`"center`" style=`"width=200;`" ><b>Status</b></td>" +"`r`n"  
    311.     $SectionReport = $SectionReport + "<td align=`"center`" style=`"width=200;`" ><b>Meetings</b></td>" +"`r`n"  
    312.     $SectionReport = $SectionReport + "</tr>"  
    313.   
    314.       
    315.     $tabNumber++  
    316.       
    317.     ""  
    318.     "User : " + $Attendeesbatch[$atndCnt].SmtpAddress  
    319.     "OOF Status : " + $attendeeOOFHash[$Attendeesbatch[$atndCnt].SmtpAddress]  
    320.     "Number of Calender Events : " + $avail.CalendarEvents.Count  
    321.   
    322.     $fbcnt = 0;  
    323.     for($stime = $displayStartTime;$stime -lt $displayStartTime.AddHours(10);$stime = $stime.AddMinutes(30)){  
    324.         $title = ""  
    325.         if ($avail.MergedFreeBusyStatus[$tmValHash[$stime.ToString("HH:mm")]] -eq "Busy" -bor $avail.MergedFreeBusyStatus[$tmValHash[$stime.ToString("HH:mm")]] -eq "OOF"){  
    326.             if ($avail.CalendarEvents.Count -ne 0){  
    327.                 for($ci=0;$ci -lt $avail.CalendarEvents.Count;$ci++){  
    328.                     if ($avail.CalendarEvents[$ci].StartTime -ge $stime -band $stime -le $avail.CalendarEvents[$ci].EndTime ){  
    329.                         if($avail.CalendarEvents[$ci].Details.IsPrivate -eq $False){  
    330.                             $subject = ""  
    331.                             $location = ""  
    332.                             if ($avail.CalendarEvents[$ci].Details.Subject -ne $null){  
    333.                                 $subject = $avail.CalendarEvents[$ci].Details.Subject.ToString()  
    334.                             }  
    335.                             if ($avail.CalendarEvents[$ci].Details.Location -ne $null){  
    336.                                 $location = $avail.CalendarEvents[$ci].Details.Location.ToString()  
    337.                             }  
    338.                             $title = $title + "`"" + $subject + " " + $location + "`" "  
    339.                         }  
    340.                     }  
    341.                 }  
    342.             }  
    343.         }  
    344.         $tbClr = "bgcolor=`"#41A317`""  
    345.         if($avail.MergedFreeBusyStatus[$tmValHash[$stime.ToString("HH:mm")]] -eq "Busy"){  
    346.             $tbClr = "bgcolor=`"#153E7E`""  
    347.         }  
    348.         $SectionReport = $SectionReport + "<tr>" +"`r`n"  
    349.         $SectionReport = $SectionReport + "<td align=`"center`" style=`"width=200;`" ><b>" + $stime.ToString("HH:mm") + " </b></td>" +"`r`n"  
    350.         $SectionReport = $SectionReport + "<td $tbClr align=`"center`" style=`"width=200;`" ><b>" + $avail.MergedFreeBusyStatus[$tmValHash[$stime.ToString("HH:mm")]] + "</b></td>" +"`r`n"  
    351.         $SectionReport = $SectionReport + "<td align=`"center`" style=`"width=200;`" ><b>" + $title + "</b></td>" +"`r`n"  
    352.         $SectionReport = $SectionReport + "</tr>"     
    353.         $stime.ToString("HH:mm") + " : " +  $avail.MergedFreeBusyStatus[$tmValHash[$stime.ToString("HH:mm")]] + " : " + $title  
    354.         $fbcnt++  
    355.     }  
    356.     $SectionReport = $SectionReport + "</table>"  
    357.     $SectionReport = $SectionReport + "</section>`r`n"  
    358.     $atndCnt++  
    359. }   
    360. $rptOutput = $rptOutput + $styleFooter + $SectionReport + "</article></body></html>"  
    361. $rptOutput | Out-File c:\temp\taboutput.htm  




    Converting FolderId's from Get-MailboxFolderStatistics to use in EWS

    $
    0
    0
    If you are using the Exchange Managed Shell Get-MailboxFolderStatistics cmdlet to track down issues or report on folders and you want to be able to get that same folder in EWS to do something else with it here's is script that you can use to do this. This script uses the ConvertId Operation in EWS to convert the OWAId you get from EMS to a EWSId which you can then use in EWS to bind to the folder.

    1. ## Get the Mailbox to Access from the 1st commandline argument  
    2.   
    3. $MailboxName = $args[0]  
    4.  
    5. ## Load Managed API dll    
    6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
    7.    
    8. ## Set Exchange Version    
    9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
    10.    
    11. ## Create Exchange Service Object    
    12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
    13.    
    14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
    15.    
    16. #Credentials Option 1 using UPN for the windows Account    
    17. $psCred = Get-Credential    
    18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
    19. $service.Credentials = $creds        
    20.    
    21. #Credentials Option 2    
    22. #service.UseDefaultCredentials = $true    
    23.    
    24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
    25.    
    26. ## Code From http://poshcode.org/624  
    27. ## Create a compilation environment  
    28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
    29. $Compiler=$Provider.CreateCompiler()  
    30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
    31. $Params.GenerateExecutable=$False  
    32. $Params.GenerateInMemory=$True  
    33. $Params.IncludeDebugInformation=$False  
    34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
    35.   
    36. $TASource=@' 
    37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
    38.     public class TrustAll : System.Net.ICertificatePolicy { 
    39.       public TrustAll() {  
    40.       } 
    41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
    42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
    43.         System.Net.WebRequest req, int problem) { 
    44.         return true; 
    45.       } 
    46.     } 
    47.   } 
    48. '@   
    49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
    50. $TAAssembly=$TAResults.CompiledAssembly  
    51.  
    52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
    53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
    54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
    55.  
    56. ## end code from http://poshcode.org/624  
    57.    
    58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
    59.    
    60. #CAS URL Option 1 Autodiscover    
    61. $service.AutodiscoverUrl($MailboxName,{$true})    
    62. "Using CAS Server : " + $Service.url     
    63.     
    64. #CAS URL Option 2 Hardcoded    
    65.    
    66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
    67. #$service.Url = $uri      
    68.    
    69. ## Optional section for Exchange Impersonation    
    70.    
    71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
    72.   
    73. function ConvertId{      
    74.     param (  
    75.             $OwaId = "$( throw 'OWAId is a mandatory Parameter' )"  
    76.           )  
    77.     process{  
    78.         $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId        
    79.         $aiItem.Mailbox = $MailboxName        
    80.         $aiItem.UniqueId = $OwaId     
    81.         $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::OwaId        
    82.         $convertedId = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EwsId)   
    83.         return $convertedId.UniqueId  
    84.     }  
    85. }  
    86.  
    87. ## Get Folder id from EMS and bind to the Folder in EWS
    88.   
    89. get-mailboxfolderstatistics $MailboxName | ForEach-Object{  
    90.     # Bind to the Inbox Folder  
    91.     $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId((Convertid $_.FolderId))     
    92.     $ewsFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
    93.     $ewsFolder  
    94. }  

    Cleaning out the Skeletons in your mailbox by deleting all the Empty folders with EWS and Get-MailboxFolderStatistics

    $
    0
    0
    One of the things you may find if your using Archive or Retention policies a lot is that you end up with skeleton folders in your mailbox. Which are basically the old folders trees that are all now empty because the items have been moved to the Archive or aged out of the Mailbox. To see your skeletons you can use the Get-MailboxFolderStatistics cmdlet and something like this to identify user created folders that are empty.

    get-mailboxfolderstatistics $MailboxName | Where-Object{$_.FolderType -eq "User Created" -band $_.ItemsInFolderAndSubFolders -eq 0}

    If you want to delete those folders there is no cmdlet to do this so you need to use EWS. Following on from my last post we can take the FolderId from Get-MailboxFolderStatistics convert this Id to a EWSId so we can then bind to the folder in EWS and then delete it using the Delete method.

    I've put together a sample script to show how to do this by first using the Get-MailboxFolderStatistics and then in EWS ConvertId and Delete (a soft folder deletion).

    The script itself isn't really intelligent in that it doesn't delete a Sub-folder before the Root folder if both are empty (it just depends they way they comes out). It just relies on the Try-Catch to ignore any errors and uses the EWS Folder TotalCount property as a secondary check.

    As this script deletes things (some of which maybe be hard to recover if you don't want them deleted) you should do your own testing and use at your own risk.

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

    1. ## Get the Mailbox to Access from the 1st commandline argument  
    2.   
    3. $MailboxName = $args[0]  
    4.  
    5. ## Load Managed API dll    
    6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
    7.    
    8. ## Set Exchange Version    
    9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
    10.    
    11. ## Create Exchange Service Object    
    12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
    13.    
    14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
    15.    
    16. #Credentials Option 1 using UPN for the windows Account    
    17. $psCred = Get-Credential    
    18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
    19. $service.Credentials = $creds        
    20.    
    21. #Credentials Option 2    
    22. #service.UseDefaultCredentials = $true    
    23.    
    24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
    25.    
    26. ## Code From http://poshcode.org/624  
    27. ## Create a compilation environment  
    28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
    29. $Compiler=$Provider.CreateCompiler()  
    30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
    31. $Params.GenerateExecutable=$False  
    32. $Params.GenerateInMemory=$True  
    33. $Params.IncludeDebugInformation=$False  
    34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
    35.   
    36. $TASource=@' 
    37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
    38.     public class TrustAll : System.Net.ICertificatePolicy { 
    39.       public TrustAll() {  
    40.       } 
    41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
    42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
    43.         System.Net.WebRequest req, int problem) { 
    44.         return true; 
    45.       } 
    46.     } 
    47.   } 
    48. '@   
    49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
    50. $TAAssembly=$TAResults.CompiledAssembly  
    51.  
    52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
    53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
    54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
    55.  
    56. ## end code from http://poshcode.org/624  
    57.    
    58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
    59.    
    60. #CAS URL Option 1 Autodiscover    
    61. $service.AutodiscoverUrl($MailboxName,{$true})    
    62. "Using CAS Server : " + $Service.url     
    63.     
    64. #CAS URL Option 2 Hardcoded    
    65.    
    66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
    67. #$service.Url = $uri      
    68.    
    69. ## Optional section for Exchange Impersonation    
    70.    
    71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
    72.   
    73. function ConvertId{      
    74.     param (  
    75.             $OwaId = "$( throw 'OWAId is a mandatory Parameter' )"  
    76.           )  
    77.     process{  
    78.         $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId        
    79.         $aiItem.Mailbox = $MailboxName        
    80.         $aiItem.UniqueId = $OwaId     
    81.         $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::OwaId        
    82.         $convertedId = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EwsId)   
    83.         return $convertedId.UniqueId  
    84.     }  
    85. }  
    86.  
    87. ## Get Empty Folders from EMS  
    88.   
    89. get-mailboxfolderstatistics $MailboxName | Where-Object{$_.FolderType -eq "User Created" -band $_.ItemsInFolderAndSubFolders -eq 0} | ForEach-Object{  
    90.     # Bind to the Inbox Folder  
    91.     "Deleting Folder " + $_.FolderPath    
    92.     try{  
    93.         $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId((Convertid $_.FolderId))     
    94.         $ewsFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)       
    95.         if($ewsFolder.TotalCount -eq 0){  
    96.             $ewsFolder.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete)  
    97.             "Folder Deleted"  
    98.         }  
    99.     }  
    100.     catch{  
    101.   
    102.     }  
    103. }  


    Creating a new Calendar,Contacts,Tasks or Notes Sub Folder in EWS with Powershell

    $
    0
    0
    I've had a few questions about this in the past week so I thought I would put together a quick sample to show how you can create a new Folder is EWS and set the FolderClass so it appears as a Calendar, Contacts, Tasks, Notes or whatever other folder you want. This is a function so you can just cut and paste it into your own script to use. The full script looks like the following which has samples of using the function at the bottom.

    1. ## Get the Mailbox to Access from the 1st commandline argument  
    2.   
    3. $MailboxName = $args[0]  
    4.   
    5. ## Load Managed API dll    
    6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
    7.     
    8. ## Set Exchange Version    
    9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
    10.     
    11. ## Create Exchange Service Object    
    12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
    13.     
    14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
    15.     
    16. #Credentials Option 1 using UPN for the windows Account    
    17. $psCred = Get-Credhttp://www.blogger.com/blogger.g?blogID=7123450#editor/target=post;postID=2287688273890342518ential    
    18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
    19. $service.Credentials = $creds        
    20.     
    21. #Credentials Option 2    
    22. #service.UseDefaultCredentials = $true    
    23.     
    24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
    25.     
    26. ## Code From http://poshcode.org/624  
    27. ## Create a compilation environment  
    28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
    29. $Compiler=$Provider.CreateCompiler()  
    30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
    31. $Params.GenerateExecutable=$False  
    32. $Params.GenerateInMemory=$True  
    33. $Params.IncludeDebugInformation=$False  
    34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
    35.   
    36. $TASource=@' 
    37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
    38.     public class TrustAll : System.Net.ICertificatePolicy { 
    39.       public TrustAll() {  
    40.       } 
    41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
    42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
    43.         System.Net.WebRequest req, int problem) { 
    44.         return true; 
    45.       } 
    46.     } 
    47.   } 
    48. '@   
    49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
    50. $TAAssembly=$TAResults.CompiledAssembly  
    51.   
    52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
    53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
    54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
    55.   
    56. ## end code from http://poshcode.org/624  
    57.     
    58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
    59.     
    60. #CAS URL Option 1 Autodiscover    
    61. $service.AutodiscoverUrl($MailboxName,{$true})    
    62. "Using CAS Server : " + $Service.url     
    63.      
    64. #CAS URL Option 2 Hardcoded    
    65.     
    66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
    67. #$service.Url = $uri      
    68.     
    69. ## Optional section for Exchange Impersonation    
    70.     
    71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
    72.   
    73. function Create-Folder{  
    74.     param (  
    75.             $ParentFolderID = "$( throw 'Enter Id of Parent Folder' )",  
    76.             $FolderName = "$( throw 'Enter Folder Name' )",  
    77.             $FolderClass = "$( throw 'Enter Folder Class IPF.Note,IPF.Appointment,IPF.Contact,IPF.Task' )"  
    78.           )  
    79.     process{  
    80.       
    81.     $NewFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service)    
    82.     $NewFolder.DisplayName = $FolderName    
    83.     $NewFolder.FolderClass = $FolderClass  
    84.     try{  
    85.         $NewFolder.Save($ParentFolderID)  
    86.         Write-Host $FolderName + " Folder Created "  
    87.     }  
    88.     catch{  
    89.         "Error : " + $Error[0]  
    90.     }  
    91.       
    92.     }  
    93. }  
    94. #Example use creating a Calendar subFolder  
    95. $ParentId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)     
    96. Create-Folder -ParentFolderID $ParentId -FolderName "New Calendar Folder Name" -FolderClass "IPF.Appointment"  
    97. #Example use creating a Contacts subFolder  
    98. $ParentId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$MailboxName)     
    99. Create-Folder -ParentFolderID $ParentId -FolderName "New Contact Folder Name" -FolderClass "IPF.Contact"  
    100. #Example use creating a Tasks subFolder  
    101. $ParentId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Tasks,$MailboxName)     
    102. Create-Folder -ParentFolderID $ParentId -FolderName "New Tasks Folder Name" -FolderClass "IPF.Task"  
    103. #Example use creating a Notes subFolder  
    104. $ParentId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Notes,$MailboxName)     
    105. Create-Folder -ParentFolderID $ParentId -FolderName "New Notes Folder Name" -FolderClass "IPF.StickyNote"  

    Using the PidTagAdditionalRenEntryIdsEx property in EWS to access localized folders such as the RSS Feeds folder in EWS and Powershell

    $
    0
    0
    As I've covered before in this post when you want to access the WellKnowFolders in EWS in a language independent way (eg not having to worry about localization) you can use the WellKnowFoldersName Enumeration in EWS http://msdn.microsoft.com/en-us/library/exchange/microsoft.exchange.webservices.data.wellknownfoldername%28v=exchg.80%29.aspx. Although most of the folderID's are now included in the enumeration one that is missing is the RSS Feeds folder Id.. The Extended property that holds the PR_EntryID for the RSS Feeds Folder is the PidTagAdditionalRenEntryIdsEx extended property on the Non_IPM_Subtree of a mailbox. This Id is held within a PersistData Block which is a structured Binary property that if you want to read in EWS you need to decode (which is fun).

    So how do you decode this property the first thing you want to do is convert it to Hex String while you could deal with it in the Binary array I find it easier to deal with as a Hex String.

    $hexVal = [System.BitConverter]::ToString($binVal).Replace("-","");

    The next thing you need to do is now start parsing the HexString because its a structure Blob you know what to expect so its just a matter of following the Map. So the first thing you need to do is read the first words (2 Bytes). Which tells you what type of Entry your dealing with from this PersistBlockType Values list this is in little-endian format so it needs to be swapped around to make sense.

    After that you need to read the next word which contains the length of the Id property (or the payload).  Once you have the length you can then parse the HexEntryId for the folder. As this property stores multiple ID's you need to do this for as many Ids as you can parse out. To then use this HexEntryID in EWS you need to use the ConvertID EWS operation which give you the EWSid of the folder from the HexEntryID which you can then Bind To the folder in EWS.

    A full sample of doing this looks like

    1. ## Get the Mailbox to Access from the 1st commandline argument  
    2.   
    3. $MailboxName = $args[0]  
    4.   
    5. ## Load Managed API dll    
    6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
    7.     
    8. ## Set Exchange Version    
    9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
    10.     
    11. ## Create Exchange Service Object    
    12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
    13.     
    14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
    15.     
    16. #Credentials Option 1 using UPN for the windows Account    
    17. $psCred = Get-Credential    
    18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
    19. $service.Credentials = $creds        
    20.     
    21. #Credentials Option 2    
    22. #service.UseDefaultCredentials = $true    
    23.     
    24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
    25.     
    26. ## Code From http://poshcode.org/624  
    27. ## Create a compilation environment  
    28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
    29. $Compiler=$Provider.CreateCompiler()  
    30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
    31. $Params.GenerateExecutable=$False  
    32. $Params.GenerateInMemory=$True  
    33. $Params.IncludeDebugInformation=$False  
    34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
    35.   
    36. $TASource=@' 
    37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
    38.     public class TrustAll : System.Net.ICertificatePolicy { 
    39.       public TrustAll() {  
    40.       } 
    41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
    42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
    43.         System.Net.WebRequest req, int problem) { 
    44.         return true; 
    45.       } 
    46.     } 
    47.   } 
    48. '@   
    49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
    50. $TAAssembly=$TAResults.CompiledAssembly  
    51.   
    52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
    53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
    54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
    55.   
    56. ## end code from http://poshcode.org/624  
    57.     
    58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
    59.     
    60. #CAS URL Option 1 Autodiscover    
    61. $service.AutodiscoverUrl($MailboxName,{$true})    
    62. "Using CAS Server : " + $Service.url     
    63.      
    64. #CAS URL Option 2 Hardcoded    
    65.     
    66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
    67. #$service.Url = $uri      
    68.     
    69. ## Optional section for Exchange Impersonation    
    70.     
    71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
    72. $PidTagAdditionalRenEntryIdsEx = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x36D9, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)  
    73. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
    74. $psPropset.Add($PidTagAdditionalRenEntryIdsEx)  
    75.   
    76. # Bind to the NON_IPM_ROOT Root folder    
    77. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)     
    78. $NON_IPM_ROOT = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)  
    79. $binVal = $null;  
    80. $AdditionalRenEntryIdsExCol = @{}  
    81. if($NON_IPM_ROOT.TryGetProperty($PidTagAdditionalRenEntryIdsEx,[ref]$binVal)){  
    82.     $hexVal = [System.BitConverter]::ToString($binVal).Replace("-","");  
    83.     ##Parse Binary Value first word is Value type Second word is the Length of the Entry  
    84.     $Sval = 0;  
    85.     while(($Sval+8) -lt $hexVal.Length){  
    86.         $PtypeVal = $hexVal.SubString($Sval,4)  
    87.         $PtypeVal = $PtypeVal.SubString(2,2) + $PtypeVal.SubString(0,2)  
    88.         $Sval +=12;  
    89.         $PropLengthVal = $hexVal.SubString($Sval,4)  
    90.         $PropLengthVal = $PropLengthVal.SubString(2,2) + $PropLengthVal.SubString(0,2)  
    91.         $PropLength = [Convert]::ToInt64($PropLengthVal, 16)  
    92.         $Sval +=4;  
    93.         $ProdIdEntry = $hexVal.SubString($Sval,($PropLength*2))  
    94.         $Sval += ($PropLength*2)  
    95.         $PtypeVal + " : " + $ProdIdEntry  
    96.         $AdditionalRenEntryIdsExCol.Add($PtypeVal,$ProdIdEntry)   
    97.     }     
    98. }  
    99.   
    100. function ConvertFolderid($hexId){  
    101.     $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId    
    102.     $aiItem.Mailbox = $MailboxName    
    103.     $aiItem.UniqueId = $hexId  
    104.     $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId;    
    105.     return $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId)   
    106. }  
    107.   
    108. if($AdditionalRenEntryIdsExCol.ContainsKey("8001")){  
    109.     $siId = ConvertFolderid($AdditionalRenEntryIdsExCol["8001"])  
    110.     $RSSFolderID = new-object Microsoft.Exchange.WebServices.Data.FolderId($siId.UniqueId.ToString())  
    111.     $RSSFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$RSSFolderID)  
    112.     $RSSFolder  
    113. }  

    Creating a Report of the WhiteSpace in all the PST's and OST in the default Outlook profile

    $
    0
    0
    One of my favorite development utilities is mfcMapi which along with OutlookSpy are invaluable utilities to use when doing any Exchange development. Along with mfcMapi Stephan also has a utility called mrMapi which provides console access to a number of useful MAPI functions . Last week he added a new feature to report on the files sizes and white-space inside a PST or OST based on the Header information in the file http://blogs.msdn.com/b/stephen_griffin/archive/2013/01/28/january-2013-release-of-mfcmapi-and-mrmapi.aspx.

    I thought this was a really useful thing as it can be a little bit confounding sometimes looking at PST files that have been produced by exporting a Mailbox and comparing that against the actual mailbox size. So I decided to put a script together that would get all the PST and OST file paths for default Outlook profile. Run those filepaths through mrMapi and parse console Output and the put together a HTML report of the information that ended up looking something like this (all sizes are in MB)


    To use this script you need the latest copy of mrMapi from http://mfcmapi.codeplex.com/ and you need to adjust the following varible in the script to the path where you downloaded it to

    $mrMapiPath = "c:\mfcmapi\mrmapi.exe"

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

    1. $mrMapiPath = "c:\mfcmapi\mrmapi.exe"  
    2. $rptCollection = @()  
    3.   
    4. function ParseBitValue($String){  
    5.     $numItempattern = '(?=\().*(?=bytes)'  
    6.     $matchedItemsNumber = [regex]::matches($String, $numItempattern)   
    7.     $bytes = [INT64]$matchedItemsNumber[0].Value.Replace("(","").Replace(",","") /1MB  
    8.     return [System.Math]::Round($bytes,2)  
    9. }  
    10.   
    11. $Encode = new-object System.Text.UnicodeEncoding  
    12. ##Check for Office 2013  
    13. $RootKey = "Software\Microsoft\Office\15.0\Outlook\Profiles\" 
    14. $pkProfileskey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($RootKey, $true) 
    15.  
    16. if($pkProfileskey -eq $null){ 
    17.     $RootKey = "Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles" 
    18.     $pkProfileskey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($RootKey, $true) 
    19.     $defProf = $pkProfileskey.GetValue("DefaultProfile") 
    20. } 
    21. else{ 
    22.     $OutDefault = "Software\Microsoft\Office\15.0\Outlook\" 
    23.     $pkProfileskey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($OutDefault, $true) 
    24.     $defProf = $pkProfileskey.GetValue("DefaultProfile") 
    25. } 
    26. $defProf 
    27. $pkSubProfilekey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey(($RootKey + "\\" + $defProf), $true) 
    28. foreach($Valuekey in $pkSubProfilekey.getSubKeyNames()){ 
    29.     $pkSubValueKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey(($RootKey + "\\" + $defProf + "\\" + $Valuekey ), $true) 
    30.     $pstVal = $pkSubValueKey.GetValue("001f6700") 
    31.     if($pstVal -ne $null) 
    32.     { 
    33.         $pstPath = $Encode.GetString($pstVal)  
    34.         $fnFileName = $pstPath.Replace([System.Convert]::ToChar(0x0).ToString().Trim(), "")      
    35.         if(Test-Path $fnFileName){ 
    36.             $rptObj = "" | Select Name,Type,FilePath,FileSize,FreeSpace,PrecentFree 
    37.             $fiInfo = ([System.IO.FileInfo]$fnFileName)  
    38.             $rptObj.Name = $fiInfo.Name 
    39.             $rptObj.FilePath = $fiInfo.DirectoryName 
    40.             $rptObj.Type = "PST"             
    41.             iex ($mrMapiPath + " -pst -i '$pstPath'") | foreach-object{ 
    42.                 if($_.Contains("=")){ 
    43.                     $splitArray = $_.Split("=")              
    44.                     switch ($splitArray[0].Trim()){                  
    45.                         "File Size" { 
    46.                                 $rptObj.FileSize =  ParseBitValue($splitArray[1].Trim())  
    47.                             } 
    48.                         "Free Space" { 
    49.                                 $rptObj.FreeSpace = ParseBitValue($splitArray[1].Trim())  
    50.                             } 
    51.                         "Percent free" { 
    52.                                 $rptObj.PrecentFree = $splitArray[1].Trim() 
    53.                             } 
    54.                     } 
    55.                      
    56.                 } 
    57.             } 
    58.             $rptCollection += $rptObj 
    59.         } 
    60.     } 
    61.     ## Check OSTs 
    62.     $ostVal = $pkSubValueKey.GetValue("001f6610") 
    63.     if($ostVal -ne $null) 
    64.     { 
    65.         $ostPath = $Encode.GetString($ostVal)  
    66.         $fnFileName = $ostPath.Replace([System.Convert]::ToChar(0x0).ToString().Trim(), "")      
    67.         if(Test-Path $fnFileName){ 
    68.             $rptObj = "" | Select Name,Type,FilePath,FileSize,FreeSpace,PrecentFree 
    69.             $fiInfo = ([System.IO.FileInfo]$fnFileName)  
    70.             $rptObj.Name = $fiInfo.Name 
    71.             $rptObj.FilePath = $fiInfo.DirectoryName 
    72.             $rptObj.Type = "OST" 
    73.             iex ($mrMapiPath + " -pst -i `"$fnFileName`"") | foreach-object{ 
    74.                 if($_.Contains("=")){ 
    75.                     $splitArray = $_.Split("=")              
    76.                      
    77.                     switch ($splitArray[0].Trim()){                  
    78.                         "File Size" { 
    79.                                 $rptObj.FileSize =  ParseBitValue($splitArray[1].Trim())  
    80.                             } 
    81.                         "Free Space" { 
    82.                                 $rptObj.FreeSpace = ParseBitValue($splitArray[1].Trim())  
    83.                             } 
    84.                         "Percent free" { 
    85.                                 $rptObj.PrecentFree = $splitArray[1].Trim() 
    86.                             } 
    87.                     } 
    88.                      
    89.                 } 
    90.             } 
    91.             $rptCollection += $rptObj 
    92.         } 
    93.     } 
    94. } 
    95.  
    96.  
    97. $tableStyle = @"  
    98. <style>  
    99. BODY{background-color:white;}  
    100. TABLE{border-width: 1px;  
    101. border-style: solid;  
    102. border-color: black;  
    103. border-collapse: collapse;  
    104. }  
    105. TH{border-width: 1px;  
    106. padding: 10px;  
    107. border-style: solid;  
    108. border-color: black;  
    109. background-color:thistle  
    110. }  
    111. TD{border-width: 1px;  
    112. padding: 2px;  
    113. border-style: solid;  
    114. border-color: black;  
    115. background-color:white  
    116. }  
    117. </style>  
    118. "@ 
    119.  
    120. $body = @"  
    121. <p style="font-size:25px;family:calibri;color:#ff9100">  
    122. $TableHeader  
    123. </p>  
    124. "@  
    125.   
    126. $rptCollection | ConvertTo-HTML -head $tableStyle –body $body |Out-File c:\temp\ProfileReport-$defProf.htm  

    Mailbox Info Mail app for Exchange 2013/Outlook 2013

    $
    0
    0
    Mail Apps for Outlook (and OWA) is one of the really cool new features of Exchange 2013.Anybody who has every had to do any OWA customization using forms from Exchange 2003 to 2010 would be glad to see they have finally gone past their used by date and Mail Apps provides the promise of a unified custom app framework across mail clients. At RTM the feature set gap compared to currently what you can do in Outlook Plugins and full access to all EWS operations is substantial (and frustrating) and a show stopper for a lot of good application ideas, but hopefully this will narrow somewhat like a lot of Exchange features when we see new bits and pieces come through in the new cumulative updates. 

    Mail Apps have three main ingredients
    • A manifest file which is what is used to install a Mail App and contains all the configuration and activation rules for the Mail App
    • A html file that is rendered by the Mail client and controls the display elements.
    • JavaScript files that are bascially the heart soul of your application and contain all the Application and Business logic. 
    If your going to write a Mail App you need to have a good grip on JavaScript and get acquainted with the JavaScript Api for Office. From a technical point of view you should also understand how the Runtime works which there is a good explanation of here .

    Browser Hell - Writing a mail app that you want to run in any browser will take a lot of testing and tweaking, although some of the vendors have improved browser standards are still pretty lacking. For example to get this small Info Apps to work across Chrome, FireFox and ie9 required dealing with Google's different implementation of parseFromString which at the moment can't deal with prefixes while IE and firefox can. This is just one example, if you going to write mail apps make sure you put on your boots and get set to wade in.

    Hosting is one other requirement for Mail Apps as the HTML,Manifest and JavaScript files that go along with your application need to be hosted somewhere (they are not stored in Exchange). Basically you just need a Web Server the most obvious source would be your CAS server but any IIS instance will do. If your Mail Apps are mission critical then availability has to factor into the argument or if your in the cloud you'll need to find a Cloud source for your apps. For this sample I've used Azure http://www.windowsazure.com/en-us/home/scenarios/web-sites/ which is a cheap and easy especially if you have a MSDN subscription. 

    For this Post I've put together an Info App that uses various methods in the Office JavaScript API and EWS to get the following information.

    DisplayName from the Item properties
    EmailAddress from the Item properties
    TimeZone from the Item properties.
    Language from the Mailbox Context.
    HostVersion from the Diag Object
    UserAgent from the Hosting Browsers window.navigator
    Total Mailbox Folder Count from EWS (FindFolder)
    MailboxServer from the EWS PR_REPLICA_SERVER Extended property 
    Inbox Total Item Count EWS (GetFolder)
    Inbox Unread Item Count EWS (GetFolder)

    This is what it looks like the App Box



    Manifest file

    Every Mail App starts with a Manifest file which contains the configuration information about the MailApp. All Mail Apps need a unique GUID which you can generate easily in Powershell using

    [System.GUID]::NewGuid()

    Permissions - Mail Apps have a three tired permission model if you want to use EWS you need to specify ReadWriteMailbox. A Mail Aps that is going to use EWS needs to be installed and enabled by an administrator. Eg for this apps the following is configured in the Manifest file

    1. <permissions>ReadWriteMailbox</permissions>
    Activation Rules -  All Mail apps need an activation rule which basically controls in what items they will appear. Activation rules are also affected by the Permissions that a Mail app requests but for this simple app I have an activation rule that says my Mail App will appear on every Mail Item

    1. <Rule xsi:type="ItemIs" ItemType="Message" /> 
    DisplaySetting - With the Display Setting in a manifest file you can control the size of the Application box and how that application box will appear on different form factors like tablets and mobiles.

    HTML and JavaScript files, for your Mail apps you need to reference the Office JavaScript file either that you host along with the application or you can use Microsoft's CDN for this sample I'm using the CDN eg.

    1. <script src="https://appsforoffice.microsoft.com/lib/1.0/hosted/office.js" type="text/javascript"></script>  
    In this App I've also made use of JQuery which is great utility script to help Glue your Mail App together, like the JavaScript API I've used the CDN version of JQuery.

    Installation - There are a couple of way to install a Mail App by far the easiest is just to use EAC eg for this sample Apps if you want to use my hosted version the URL would be

    https://gsmailapp.azurewebsites.net/InfoApp/manifest.xml

    You should however download and host it yourself I've put a download of the files here

    Removing attachments of a specific type with EWS and Powershell

    $
    0
    0
    Sometimes particular attachments can create different issues for a number of different reasons so you might find you want to get rid of all those attachments without removing the messages they are attached to. If you find yourself in this situation then this is the sample for you.

    Firstly to find the attachments of a specific type we use the following AQS query

    System.Message.AttachmentContents:.pdf

    This acts as a first level filter but it will give a few false positives so a second client level check of the actual Attachment file name is needed.

    1. foreach ($Attachment in $Item.Attachments)  
    2. {  
    3.              if($Attachment -is [Microsoft.Exchange.WebServices.Data.FileAttachment]){    
    4.         if($Attachment.Name.Contains(".pdf")){  
    5.  
    6.         }  
    7.              }    
    8. }  
    The following script can be run in one of two modes , Report will just report on attachments that are found in a particular folder and Delete will delete attachments from messages within that folder. I've put a download of this script here the code itself looks like.

    1. $RunMode = Read-Host "Enter Mode for Script (Report or Delete)"  
    2. switch($RunMode){  
    3.     "Report" {$runokay = $true  
    4.               "Report Mode Detected"  
    5.             }  
    6.     "Delete" {$runokay = $true  
    7.              "Delete Mode Detected"  
    8.              }  
    9.     default {$runOkay = $false  
    10.              "Invalid Reponse you need to type Report or Delete"  
    11.              }  
    12. }  
    13. if($runokay){  
    14. ## Get the Mailbox to Access from the 1st commandline argument  
    15.   
    16. $MailboxName = (Read-Host "Enter mailbox to run it against(email address)" )  
    17.   
    18. $rptcollection = @()  
    19.   
    20. ## Load Managed API dll    
    21. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"    
    22. [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")   
    23. [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")   
    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. $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);    
    91. $folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)    
    92. # Bind to the Contacts Folder  
    93.   
    94. $rfRootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderidcnt)  
    95.   
    96. #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling    
    97. $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)    
    98. #Deep Transval will ensure all folders in the search path are returned    
    99. $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;    
    100. $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
    101. $PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
    102. #Add Properties to the  Property Set    
    103. $psPropertySet.Add($PR_Folder_Path);    
    104. $fvFolderView.PropertySet = $psPropertySet;    
    105. #The Search filter will exclude any Search Folders    
    106. $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")    
    107. $fiResult = $null    
    108. #  
    109.   
    110. $Treeinfo = @{}  
    111. $TNRoot = new-object System.Windows.Forms.TreeNode("Root")  
    112. $TNRoot.Name = "Mailbox"  
    113. $TNRoot.Text = "Mailbox - " + $MailboxName  
    114. #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox    
    115. do {    
    116.     $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)    
    117.     foreach($ffFolder in $fiResult.Folders){     
    118.         #Process folder here  
    119.         $TNChild = new-object System.Windows.Forms.TreeNode($ffFolder.DisplayName.ToString())  
    120.         $TNChild.Name = $ffFolder.DisplayName.ToString()  
    121.         $TNChild.Text = $ffFolder.DisplayName.ToString()  
    122.         $TNChild.tag = $ffFolder.Id.UniqueId.ToString()  
    123.         if ($ffFolder.ParentFolderId.UniqueId -eq $rfRootFolder.Id.UniqueId ){  
    124.             $ffFolder.DisplayName  
    125.             [void]$TNRoot.Nodes.Add($TNChild)   
    126.             $Treeinfo.Add($ffFolder.Id.UniqueId.ToString(),$TNChild)  
    127.         }  
    128.         else{  
    129.             $pfFolder = $Treeinfo[$ffFolder.ParentFolderId.UniqueId.ToString()]  
    130.             [void]$pfFolder.Nodes.Add($TNChild)  
    131.             if ($Treeinfo.ContainsKey($ffFolder.Id.UniqueId.ToString()) -eq $false){  
    132.                 $Treeinfo.Add($ffFolder.Id.UniqueId.ToString(),$TNChild)  
    133.             }  
    134.         }  
    135.     }   
    136.     $fvFolderView.Offset += $fiResult.Folders.Count  
    137. }while($fiResult.MoreAvailable -eq $true)    
    138. $Script:clickedFolder = $null  
    139. $objForm = New-Object System.Windows.Forms.Form   
    140. $objForm.Text = "Folder Select Form"  
    141. $objForm.Size = New-Object System.Drawing.Size(600,600)   
    142. $objForm.StartPosition = "CenterScreen"  
    143. $tvTreView1 = new-object System.Windows.Forms.TreeView  
    144. $tvTreView1.Location = new-object System.Drawing.Size(1,1)   
    145. $tvTreView1.add_DoubleClick({  
    146.     $Script:clickedFolder = $this.SelectedNode.tag  
    147.     $objForm.Close()  
    148. })  
    149. $tvTreView1.size = new-object System.Drawing.Size(580,580)   
    150. $tvTreView1.Anchor = "Top,left,Bottom"  
    151. [void]$tvTreView1.Nodes.Add($TNRoot)   
    152. $objForm.controls.add($tvTreView1)  
    153. $objForm.ShowDialog()  
    154.   
    155. $clickedfolderid = new-object Microsoft.Exchange.WebServices.Data.FolderId($Script:clickedFolder)   
    156. #Define Attachment Contet To Search for  
    157.   
    158. $AttachmentContent = ".pdf"  
    159. $AQSString = "System.Message.AttachmentContents:$AttachmentContent"  
    160.   
    161. #Define ItemView to retrive just 1000 Items      
    162. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)      
    163. $fiItems = $null      
    164. do{      
    165.     $fiItems = $service.FindItems($clickedfolderid,$AQSString,$ivItemView)      
    166.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
    167.     foreach($Item in $fiItems.Items){    
    168.             $Item.Load()  
    169.               
    170.             $attachtoDelete = @()  
    171.             foreach ($Attachment in $Item.Attachments)  
    172.             {  
    173.                 if($Attachment -is [Microsoft.Exchange.WebServices.Data.FileAttachment]){    
    174.                     if($Attachment.Name.Contains(".pdf")){  
    175.                         if($RunMode -eq "Report"){  
    176.                             $rptObj = "" | Select DateTimeReceived,Subject,AttachmentName,Size  
    177.                             $rptObj.DateTimeReceived = $Item.DateTimeReceived  
    178.                             $rptObj.Subject = $Item.Subject  
    179.                             $rptObj.AttachmentName = $Attachment.Name                         
    180.                             $rptObj.Size = $Attachment.Size  
    181.                             $rptcollection += $rptObj  
    182.                         }  
    183.                         $Attachment.Name  
    184.                         if($RunMode -eq "Delete"){  
    185.                             $attachtoDelete += $Attachment  
    186.                         }  
    187.                     }  
    188.                 }    
    189.             }  
    190.             $updateItem = $false  
    191.             foreach($AttachmentToDelete in $attachtoDelete){  
    192.                 if($RunMode -eq "Delete"){  
    193.                     $Item.Attachments.Remove($AttachmentToDelete)  
    194.                     $updateItem = $true  
    195.                 }  
    196.             }  
    197.             if($updateItem){$Item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverWrite)};  
    198.     }      
    199.     $ivItemView.Offset += $fiItems.Items.Count      
    200. }while($fiItems.MoreAvailable -eq $true)  
    201. if($RunMode -eq "Report"){  
    202.     $rptcollection | Export-Csv -Path c:\temp\$MailboxName-AttachmentReport.csv -NoTypeInformation  
    203. }  
    204.   
    205. }  





    Clearing the RSS Feeds folder in Exchange using EWS and Powershell

    $
    0
    0
    RSS feeds in Outlook is a nice feature that was added in Outlook, but for some organizations it may not be a desirable feature. While you can't delete the RSS Feeds folder itself as its on of Outlooks default folders you can easily remove each of the feeds and items from these folders using EWS's Empty folder operation http://msdn.microsoft.com/en-us/library/exchange/ff797574(v=exchg.80).aspx. A couple of months back I posted a Language independent way of getting the RSS feeds folder using the PidTagAdditionalRenEntryIdsEx property http://gsexdev.blogspot.com.au/2013/01/using-pidtagadditionalrenentryidsex.html . This script can be easily modified to Empty the RSSFeeds folder eg  the following script binds to the RSS Feeds folder and use the Empty Operation with the parameter to also delete all sub folders under the RSS Feeds Folder. - I've put a download of this script here.


    1. ## Get the Mailbox to Access from the 1st commandline argument    
    2.     
    3. $MailboxName = $args[0]    
    4.     
    5. ## Load Managed API dll      
    6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"      
    7.       
    8. ## Set Exchange Version      
    9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2      
    10.       
    11. ## Create Exchange Service Object      
    12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)      
    13.       
    14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials      
    15.       
    16. #Credentials Option 1 using UPN for the windows Account      
    17. $psCred = Get-Credential      
    18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())      
    19. $service.Credentials = $creds          
    20.       
    21. #Credentials Option 2      
    22. #service.UseDefaultCredentials = $true      
    23.       
    24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates      
    25.       
    26. ## Code From http://poshcode.org/624    
    27. ## Create a compilation environment    
    28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider    
    29. $Compiler=$Provider.CreateCompiler()    
    30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters    
    31. $Params.GenerateExecutable=$False    
    32. $Params.GenerateInMemory=$True    
    33. $Params.IncludeDebugInformation=$False    
    34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null    
    35.     
    36. $TASource=@ 
    37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{  
    38.     public class TrustAll : System.Net.ICertificatePolicy {  
    39.       public TrustAll() {   
    40.       }  
    41.       public bool CheckValidationResult(System.Net.ServicePoint sp,  
    42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,   
    43.         System.Net.WebRequest req, int problem) {  
    44.         return true;  
    45.       }  
    46.     }  
    47.   }  
    48. '@     
    49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)    
    50. $TAAssembly=$TAResults.CompiledAssembly    
    51.     
    52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager    
    53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")    
    54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll    
    55.     
    56. ## end code from http://poshcode.org/624    
    57.       
    58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use      
    59.       
    60. #CAS URL Option 1 Autodiscover      
    61. $service.AutodiscoverUrl($MailboxName,{$true})      
    62. "Using CAS Server : " + $Service.url       
    63.        
    64. #CAS URL Option 2 Hardcoded      
    65.       
    66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"      
    67. #$service.Url = $uri        
    68.       
    69. ## Optional section for Exchange Impersonation      
    70.       
    71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)     
    72. $PidTagAdditionalRenEntryIdsEx = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x36D9, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)    
    73. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
    74. $psPropset.Add($PidTagAdditionalRenEntryIdsEx)    
    75.     
    76. # Bind to the NON_IPM_ROOT Root folder      
    77. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)       
    78. $NON_IPM_ROOT = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)    
    79. $binVal = $null;    
    80. $AdditionalRenEntryIdsExCol = @{}    
    81. if($NON_IPM_ROOT.TryGetProperty($PidTagAdditionalRenEntryIdsEx,[ref]$binVal)){    
    82.     $hexVal = [System.BitConverter]::ToString($binVal).Replace("-","");    
    83.     ##Parse Binary Value first word is Value type Second word is the Length of the Entry    
    84.     $Sval = 0;    
    85.     while(($Sval+8) -lt $hexVal.Length){    
    86.         $PtypeVal = $hexVal.SubString($Sval,4)    
    87.         $PtypeVal = $PtypeVal.SubString(2,2) + $PtypeVal.SubString(0,2)    
    88.         $Sval +=12;    
    89.         $PropLengthVal = $hexVal.SubString($Sval,4)    
    90.         $PropLengthVal = $PropLengthVal.SubString(2,2) + $PropLengthVal.SubString(0,2)    
    91.         $PropLength = [Convert]::ToInt64($PropLengthVal, 16)    
    92.         $Sval +=4;    
    93.         $ProdIdEntry = $hexVal.SubString($Sval,($PropLength*2))    
    94.         $Sval += ($PropLength*2)    
    95.         #$PtypeVal + " : " + $ProdIdEntry    
    96.         $AdditionalRenEntryIdsExCol.Add($PtypeVal,$ProdIdEntry)     
    97.     }       
    98. }    
    99.     
    100. function ConvertFolderid($hexId){    
    101.     $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId      
    102.     $aiItem.Mailbox = $MailboxName      
    103.     $aiItem.UniqueId = $hexId    
    104.     $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId;      
    105.     return $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId)     
    106. }    
    107.   
    108. #Define Function to convert String to FolderPath    
    109. function ConvertToString($ipInputString){    
    110.     $Val1Text = ""    
    111.     for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){    
    112.             $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))    
    113.             $clInt++    
    114.     }    
    115.     return $Val1Text    
    116. }   
    117.   
    118.     
    119. if($AdditionalRenEntryIdsExCol.ContainsKey("8001")){    
    120.     $siId = ConvertFolderid($AdditionalRenEntryIdsExCol["8001"])    
    121.     $RSSFolderID = new-object Microsoft.Exchange.WebServices.Data.FolderId($siId.UniqueId.ToString())    
    122.     $RSSFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$RSSFolderID)   
    123.     if($RSSFolder -ne $null){  
    124.         "Deleteing : " + $RSSFolder.ChildFolderCount.ToString() + " Feeds"  
    125.         $RSSFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete, $true);  
    126.     }  
    127. }    

    Using the EWS FindPeople operation in Exchange 2013 and Exchange Online (2013)

    $
    0
    0
    In previous versions of Exchange accessing the Directory (or Global Address List) with EWS was limited to just resolving a specific entry or expanding a Distribution list. In Exchange 2013 the concept of the persona's has been introduced http://msdn.microsoft.com/en-us/library/exchange/jj190898%28v=exchg.150%29.aspx. To distill this down a little a persona allows you to have a Contact that can pull its related information from multiple sources while previously everything was just stored in one object in the Exchange Store.

    There are a number of new EWS operations to go along with personas and the Unified Contact Store the most interesting of these is the FindPeople operation which allows you to do something you couldn't do previously which is enumerate the entire GAL (or any other address list).  At this point I should make the point that the GAL is just one of the data sources that FindPeople can search you can also include searching the local contact folders. 

    To able to search an Address List (including the GAL but it will work with any of the Address Lists like ("All Room", "All Contacts") you need to know the AddressList Id.  However this is one part of these new operations that really needs improvement because at the moment there is no way in EWS to get a list of AddressList Id's. While the FindPeople operation is used in OWA in 2013 they have a custom command in OWA called "GetPeopleFilters". So if you want to use the FindPeople operation you need to either Get the AddressList id from using OWA and capturing it with fiddler eg


    (If your using Office365 this is the only way you can get this information that i know of as the Get-AddressList cmdlet or LDAP/AD access isn't available in the cloud)

    In a OnPrem environment you can just use the Get-AddressList cmdlet eg

    (or you could also use LDAP to get this information)

    Once you have the address list Id your ready to code, there are no methods in the EWS Managed API for this operation so you need to use WSDL ProxyCode or RAW Soap to use this operation. For example the following is a C# sample for using the FindPeople operation to enumerate through the address list 100 entries at the a time


    1. FindPeopleType fpType = new FindPeopleType();  
    2. IndexedPageViewType indexPageView = new IndexedPageViewType();  
    3. indexPageView.BasePoint = IndexBasePointType.Beginning;  
    4. indexPageView.Offset = 0;  
    5. indexPageView.MaxEntriesReturned = 100;  
    6. indexPageView.MaxEntriesReturnedSpecified = true;  
    7. fpType.IndexedPageItemView = indexPageView;  
    8.              
    9.              
    10. fpType.ParentFolderId = new TargetFolderIdType();  
    11. DistinguishedFolderIdType contactsFolder = new DistinguishedFolderIdType();  
    12. AddressListIdType adList = new AddressListIdType();  
    13. adList.Id = "2117949e-abe8-4915-91eb-6b9f867fd8de";  
    14.               
    15. fpType.ParentFolderId.Item = adList;  
    16. FindPeopleResponseMessageType fpm = null;  
    17. do  
    18. {  
    19.     fpm = esb.FindPeople(fpType);  
    20.     if (fpm.ResponseClass == ResponseClassType.Success)  
    21.     {  
    22.         foreach (PersonaType PsCnt in fpm.People) {  
    23.             Console.WriteLine(PsCnt.EmailAddress.EmailAddress);  
    24.         }  
    25.         indexPageView.Offset += fpm.People.Length;                      
    26.     }  
    27.     else {  
    28.         throw new Exception("Error");  
    29.     }  
    30. while (fpm.TotalNumberOfPeopleInView > indexPageView.Offset);  


    EWS/Powershell Recoverable Items age report for Exchange 2010/13

    $
    0
    0
    With Single Item Recovery in Exchange 2010 and Exchange 2013, the Recoverable Items Folder in an Exchange Mailbox becomes one of the things you may want to pay some more special attention to in the course of managing the disk resource that are being consumed within your Exchange Org. The following is a script that does another take on ItemAge reporting by basically crawling every item in a folder and then grouping the DateTimeRecieved (to get the age of the Item) and the ModifiedTime(which is when its deleted). The script produces a report like the following for the Mailbox you run it against

    To use the script you just need to enter the SMTPAddress of the Mailbox you want to run it against and as long as you have rights to the mailbox you should get a report.

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

    1. ## Get the Mailbox to Access from the 1st commandline argument  
    2.   
    3. $MailboxName = $args[0]  
    4.   
    5. ## Load Managed API dll    
    6. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"    
    7.     
    8. ## Set Exchange Version    
    9. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2    
    10.     
    11. ## Create Exchange Service Object    
    12. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)    
    13.     
    14. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials    
    15.     
    16. #Credentials Option 1 using UPN for the windows Account    
    17. $psCred = Get-Credential    
    18. $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())    
    19. $service.Credentials = $creds        
    20.     
    21. #Credentials Option 2    
    22. #service.UseDefaultCredentials = $true    
    23.     
    24. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
    25.     
    26. ## Code From http://poshcode.org/624  
    27. ## Create a compilation environment  
    28. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
    29. $Compiler=$Provider.CreateCompiler()  
    30. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
    31. $Params.GenerateExecutable=$False  
    32. $Params.GenerateInMemory=$True  
    33. $Params.IncludeDebugInformation=$False  
    34. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
    35.   
    36. $TASource=@' 
    37.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
    38.     public class TrustAll : System.Net.ICertificatePolicy { 
    39.       public TrustAll() {  
    40.       } 
    41.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
    42.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
    43.         System.Net.WebRequest req, int problem) { 
    44.         return true; 
    45.       } 
    46.     } 
    47.   } 
    48. '@   
    49. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
    50. $TAAssembly=$TAResults.CompiledAssembly  
    51.   
    52. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
    53. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
    54. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
    55.   
    56. ## end code from http://poshcode.org/624  
    57.     
    58. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use    
    59.     
    60. #CAS URL Option 1 Autodiscover    
    61. $service.AutodiscoverUrl($MailboxName,{$true})    
    62. "Using CAS Server : " + $Service.url     
    63.      
    64. #CAS URL Option 2 Hardcoded    
    65.     
    66. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"    
    67. #$service.Url = $uri      
    68.     
    69. ## Optional section for Exchange Impersonation    
    70.     
    71. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)   
    72. $PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);  
    73. $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
    74. $psPropset.Add($PR_MESSAGE_SIZE_EXTENDED)  
    75. # Bind to the MsgFolderRoot folder    
    76. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsDeletions,$MailboxName)     
    77. $RecoverableItemsDeletions = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid,$psPropset)  
    78.   
    79. $rptObject = "" | Select MailboxName,TotalNumberOfItems,TotalSize,AgeLessThan7days,AgeLessThan7daysSize,Age7To30Days,Age7To30DaysSize,Age1to6Months,Age1to6MonthsSize,Age6To12Months,Age6To12MonthsSize,AgeGreator12Months,AgeGreator12MonthsSize,DeletedLessThan7days,DeletedLessThan7daysSize,Deleted7To30Days,Deleted7To30DaysSize,Deleted1to6Months,Deleted1to6MonthsSize,Deleted6To12Months,Deleted6To12MonthsSize,DeletedGreator12Months,DeletedGreator12MonthsSize  
    80. $rptObject.AgeLessThan7daysSize = [INT64]0  
    81. $rptObject.Age7To30DaysSize = [INT64]0  
    82. $rptObject.Age1to6MonthsSize = [INT64]0  
    83. $rptObject.Age6To12MonthsSize = [INT64]0  
    84. $rptObject.AgeGreator12MonthsSize = [INT64]0  
    85. $rptObject.AgeLessThan7days = [INT64]0  
    86. $rptObject.Age7To30Days = [INT64]0  
    87. $rptObject.Age1to6Months = [INT64]0  
    88. $rptObject.Age6To12Months = [INT64]0  
    89. $rptObject.AgeGreator12Months = [INT64]0  
    90. $rptObject.DeletedLessThan7daysSize = [INT64]0  
    91. $rptObject.Deleted7To30DaysSize = [INT64]0  
    92. $rptObject.Deleted1to6MonthsSize = [INT64]0  
    93. $rptObject.Deleted6To12MonthsSize = [INT64]0  
    94. $rptObject.DeletedGreator12MonthsSize = [INT64]0  
    95. $rptObject.DeletedLessThan7days = [INT64]0  
    96. $rptObject.Deleted7To30Days = [INT64]0  
    97. $rptObject.Deleted1to6Months = [INT64]0  
    98. $rptObject.Deleted6To12Months = [INT64]0  
    99. $rptObject.DeletedGreator12Months = [INT64]0  
    100. $rptObject.MailboxName = $MailboxName  
    101. $rptObject.TotalNumberOfItems = $RecoverableItemsDeletions.TotalCount  
    102. $folderSizeVal = $null;  
    103. if($RecoverableItemsDeletions.TryGetProperty($PR_MESSAGE_SIZE_EXTENDED,[ref]$folderSizeVal)){  
    104.     $rptObject.TotalSize = [Math]::Round([Int64]$folderSizeVal / 1mb,2)  
    105. }  
    106. #Define ItemView to retrive just 1000 Items      
    107. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)  
    108. $itemPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly)   
    109. $itemPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived)  
    110. $itemPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeCreated)  
    111. $itemPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime)  
    112. $itemPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size)  
    113. $fiItems = $null      
    114. $itemCnt = 0  
    115. do{   
    116.     $fiItems = $service.FindItems($RecoverableItemsDeletions.Id,$ivItemView)      
    117.     #[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)    
    118.     foreach($Item in $fiItems.Items){      
    119.         $Notadded = $true  
    120.         $dateVal = $null  
    121.         if($Item.TryGetProperty([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,[ref]$dateVal )-eq $false){  
    122.             $dateVal = $Item.DateTimeCreated  
    123.         }  
    124.         $modDateVal = $null  
    125.         if($Item.TryGetProperty([Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime,[ref]$modDateVal)){  
    126.             if($modDateVal -gt (Get-Date).AddDays(-7))  
    127.             {  
    128.                 $rptObject.DeletedLessThan7days++  
    129.                 $rptObject.DeletedLessThan7daysSize += $Item.Size  
    130.                 $Notadded = $false  
    131.             }  
    132.             if($modDateVal -le (Get-Date).AddDays(-7) -band $modDateVal -gt (Get-Date).AddDays(-30))  
    133.             {  
    134.                 $rptObject.Deleted7To30Days++  
    135.                 $rptObject.Deleted7To30DaysSize += $Item.Size  
    136.                 $Notadded = $false  
    137.             }  
    138.             if($modDateVal -le (Get-Date).AddDays(-30) -band $modDateVal -gt (Get-Date).AddMonths(-6))  
    139.             {  
    140.                 $rptObject.Deleted1to6Months++  
    141.                 $rptObject.Deleted1to6MonthsSize += $Item.Size  
    142.                 $Notadded = $false  
    143.             }  
    144.             if($modDateVal -le (Get-Date).AddMonths(-6) -band $modDateVal -gt (Get-Date).AddMonths(-12))  
    145.             {  
    146.                 $rptObject.Deleted6To12Months++  
    147.                 $rptObject.Deleted6To12MonthsSize += $Item.Size   
    148.                 $Notadded = $false  
    149.             }  
    150.             if($modDateVal -le (Get-Date).AddYears(-1))  
    151.             {  
    152.                 $rptObject.DeletedGreator12Months++  
    153.                 $rptObject.DeletedGreator12MonthsSize += $Item.Size   
    154.                 $Notadded = $false  
    155.             }   
    156.         }         
    157.         if($dateVal -gt (Get-Date).AddDays(-7))  
    158.         {  
    159.             $rptObject.AgeLessThan7days++  
    160.             $rptObject.AgeLessThan7daysSize += $Item.Size   
    161.             $Notadded = $false  
    162.         }  
    163.         if($dateVal -le (Get-Date).AddDays(-7) -band $dateVal -gt (Get-Date).AddDays(-30))  
    164.         {  
    165.             $rptObject.Age7To30Days++  
    166.             $rptObject.Age7To30DaysSize += $Item.Size   
    167.             $Notadded = $false  
    168.         }  
    169.         if($dateVal -le (Get-Date).AddDays(-30) -band $dateVal -gt (Get-Date).AddMonths(-6))  
    170.         {  
    171.             $rptObject.Age1to6Months++            
    172.             $rptObject.Age1to6MonthsSize += $Item.Size   
    173.             $Notadded = $false  
    174.         }  
    175.         if($dateVal -le (Get-Date).AddMonths(-6) -band $dateVal -gt (Get-Date).AddMonths(-12))  
    176.         {  
    177.             $rptObject.Age6To12Months++  
    178.             $rptObject.Age6To12MonthsSize += $Item.Size   
    179.             $Notadded = $false  
    180.         }  
    181.         if($dateVal -le (Get-Date).AddYears(-1))  
    182.         {  
    183.             $rptObject.AgeGreator12Months++  
    184.             $rptObject.AgeGreator12MonthsSize += $Item.Size   
    185.             $Notadded = $false  
    186.         }   
    187.     }      
    188.     $ivItemView.Offset += $fiItems.Items.Count      
    189. }while($fiItems.MoreAvailable -eq $true)   
    190. if($rptObject.AgeLessThan7daysSize -gt 0){  
    191.     $rptObject.AgeLessThan7daysSize = [Math]::Round($rptObject.AgeLessThan7daysSize/ 1mb,2)  
    192. }  
    193. if($rptObject.Age7To30DaysSize -gt 0){  
    194.     $rptObject.Age7To30DaysSize = [Math]::Round($rptObject.Age7To30DaysSize/ 1mb,2)  
    195. }  
    196. if($rptObject.Age1to6MonthsSize -gt 0){  
    197.     $rptObject.Age1to6MonthsSize = [Math]::Round($rptObject.Age1to6MonthsSize/ 1mb,2)  
    198. }  
    199. if($rptObject.Age6To12MonthsSize -gt 0){  
    200.     $rptObject.Age6To12MonthsSize = [Math]::Round($rptObject.Age6To12MonthsSize/ 1mb,2)  
    201. }  
    202. if($rptObject.AgeGreator12MonthsSize -gt 0){  
    203.     $rptObject.AgeGreator12MonthsSize = [Math]::Round($rptObject.AgeGreator12MonthsSize/ 1mb,2)  
    204. }  
    205. if($rptObject.DeletedLessThan7daysSize -gt 0){  
    206.     $rptObject.DeletedLessThan7daysSize = [Math]::Round($rptObject.DeletedLessThan7daysSize/ 1mb,2)  
    207. }  
    208. if($rptObject.Deleted7To30DaysSize -gt 0){  
    209.     $rptObject.Deleted7To30DaysSize = [Math]::Round($rptObject.Deleted7To30DaysSize/ 1mb,2)  
    210. }  
    211. if($rptObject.Deleted1to6MonthsSize -gt 0){  
    212.     $rptObject.Deleted1to6MonthsSize = [Math]::Round($rptObject.Deleted1to6MonthsSize/ 1mb,2)  
    213. }  
    214. if($rptObject.Deleted6To12MonthsSize -gt 0){  
    215.     $rptObject.Deleted6To12MonthsSize = [Math]::Round($rptObject.Deleted6To12MonthsSize/ 1mb,2)  
    216. }  
    217. if($rptObject.DeletedGreator12MonthsSize -gt 0){  
    218.     $rptObject.DeletedGreator12MonthsSize = [Math]::Round($rptObject.DeletedGreator12MonthsSize/ 1mb,2)  
    219. }  
    220. $a = "<style>"  
    221. $a = $a + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"  
    222. $a = $a + "TH{border-width: 1px;padding: 0px;border-style: solid;border-color: black;}"  
    223. $a = $a + "TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black;}"  
    224. $a = $a + "</style>"  
    225. $rptObject | ConvertTo-Html -As LIST -Head $a | Out-File c:\temp\RetainedItemReport.htm  

    EWS Findpeople workaround for reading the Offline Address Book in Office365 / Exchange Online

    $
    0
    0
    A couple of weeks ago I posted this about using the new FindPeople operation in EWS with Exchange 2013 to enumerate through the Global Address List. As I mentioned one pain point around using this new operation on Office365 is that you need to know the AddressList Id which there is no way of dynamically getting via EWS. This has been bugging me for a while so I started thinking about some ways of working around this and one method I found that did work is you can obtain the Id for the Offline Address Book and then query this (which is mostly as good as querying the Online GAL).

    To Get the Id of the Offline Address book what you first need to do is use AutoDiscover to get the External OAB url, Then use a normal Get request on this url for the oab.xml file. Then you can parse from the OAB.xml file the Guid value of the OAB which you can transform into an AddressList id that you can then use with EWS to query the OAB. The following C# sample use the EWS Managed API for Autodiscover and then use some WSDL Proxy code to execute the FindPeople Operation

    1.     NetworkCredential ncCred = new NetworkCredential("user@domain.onmicrosoft.com""psword");  
    2.     String mbMailbox = "user@domain.onmicrosoft.com";  
    3.     AutodiscoverService adService = new AutodiscoverService(ExchangeVersion.Exchange2013);  
    4.     adService.Credentials = ncCred;  
    5.     adService.RedirectionUrlValidationCallback = adAutoDiscoCallBack;  
    6.     GetUserSettingsResponse adResponse = adService.GetUserSettings(mbMailbox, (new UserSettingName[2] { UserSettingName.ExternalOABUrl,UserSettingName.ExternalEwsUrl }));  
    7.     String exOABURL = (String)adResponse.Settings[UserSettingName.ExternalOABUrl];  
    8.     String ewsURL = (String)adResponse.Settings[UserSettingName.ExternalEwsUrl];  
    9.     String auDisXML = "";  
    10.     System.Net.HttpWebRequest oabRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create((exOABURL + "oab.xml"));  
    11.   
    12.      
    13.     byte[] bytes = Encoding.UTF8.GetBytes(auDisXML);  
    14.     oabRequest.ContentLength = bytes.Length;  
    15.     oabRequest.ContentType = "text/xml";  
    16.     oabRequest.Headers.Add("Translate""F");  
    17.     oabRequest.Method = "GET";  
    18.     oabRequest.Credentials = ncCred;  
    19.     oabRequest.AllowAutoRedirect = false;  
    20.     WebResponse oabResponse = oabRequest.GetResponse();  
    21.   
    22.     Stream rsResponseStream = oabResponse.GetResponseStream();  
    23.     XmlDocument reResponseDoc = new XmlDocument();  
    24.     reResponseDoc.Load(rsResponseStream);  
    25.     XmlNodeList oabDetails = reResponseDoc.GetElementsByTagName("OAL");  
    26.     String OabGuid = oabDetails[0].Attributes["dn"].Value.Substring(6);  
    27.     OabGuid =  OabGuid.Substring(6, 2) + OabGuid.Substring(4, 2) + OabGuid.Substring(2, 2) + OabGuid.Substring(0, 2) + "-" + OabGuid.Substring(10, 2) + OabGuid.Substring(8, 2) + "-" + OabGuid.Substring(14, 2) + OabGuid.Substring(12, 2) + "-" + OabGuid.Substring(16, 4) + "-" + OabGuid.Substring(20, 12);  
    28.   
    29.   
    30.     ExchangeServiceBinding esb = new ExchangeServiceBinding();  
    31.     esb.Url = ewsURL;  
    32.     esb.Credentials = ncCred;  
    33.     esb.RequestServerVersionValue = new EWSProxy.RequestServerVersion();  
    34.     esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2013;  
    35.   
    36.     FindPeopleType fpType = new FindPeopleType();  
    37.     IndexedPageViewType indexPageView = new IndexedPageViewType();  
    38.     indexPageView.BasePoint = IndexBasePointType.Beginning;  
    39.     indexPageView.Offset = 0;  
    40.     indexPageView.MaxEntriesReturned = 100;  
    41.     indexPageView.MaxEntriesReturnedSpecified = true;  
    42.     fpType.IndexedPageItemView = indexPageView;  
    43.   
    44.   
    45.     fpType.ParentFolderId = new TargetFolderIdType();  
    46.     DistinguishedFolderIdType contactsFolder = new DistinguishedFolderIdType();  
    47.     AddressListIdType adList = new AddressListIdType();  
    48.     adList.Id = OabGuid;  
    49.   
    50.     fpType.ParentFolderId.Item = adList;  
    51.     FindPeopleResponseMessageType fpm = null;  
    52.     do  
    53.     {  
    54.         fpm = esb.FindPeople(fpType);  
    55.         if (fpm.ResponseClass == ResponseClassType.Success)  
    56.         {  
    57.             foreach (PersonaType PsCnt in fpm.People)  
    58.             {  
    59.                 if (PsCnt.EmailAddress.MailboxTypeSpecified) {  
    60.                     Console.WriteLine(PsCnt.EmailAddress.MailboxType);  
    61.                 }  
    62.                 Console.WriteLine( PsCnt.EmailAddress.EmailAddress);  
    63.             }  
    64.             indexPageView.Offset += fpm.People.Length;  
    65.         }  
    66.         else  
    67.         {  
    68.             throw new Exception("Error");  
    69.         }  
    70.     } while (fpm.TotalNumberOfPeopleInView > indexPageView.Offset);    
    71.   
    72.   
    73. }  
    74. internal static bool adAutoDiscoCallBack(string url)  
    75. {  
    76.     return true;  
    77. }  


    EWS Powershell default Mailbox Folder ACE audit script

    $
    0
    0
    The default Access Control Entry ACE in an Exchange Mailbox Folder DACL controls the rights that all authenticated users have to a particularity mailbox folder. Generally from a security perspective its something you don't want users to use as they may not understand when then set this ACE they are giving all people access to certain information. Eg Giving the default user reviewer rights to your Inbox means everyone in the Exchange org can now read mailbox in you Inbox which may not provide much privacy to your email. The following script will enumerate the permissions on every folder in a Mailbox and then produce a report of the folders where the default ACE isn't set to none. For most users the only folder that should show up in the report in the calendar folder. This script uses EWS but you could also use the Exchange Management Shell Get-MailboxFolderPermission cmdlet.

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

    Creating Item Attachments with the EWS Managed API with Powershell

    $
    0
    0
    Item Attachments in EWS allow you to create an Attachment that is of a Rich Exchange type such as an EmailMessage, Task, Post, Contact etc or even your own custom form if your using these. Although one thing you can't do with an Item attachment is attach an existing Exchange Item.

    To use ItemAttachments in Powershell you need to use reflection in .NET as it involves invoking a Generic Method which is not that straight forward in Powershell.

    The following is an example of creating a Task as an Item Attachment in Powershell using reflection

    1. $mail = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service);  
    2. $AtColtype = ("Microsoft.Exchange.WebServices.Data.AttachmentCollection") -as "Type"  
    3. $Tasktype = ("Microsoft.Exchange.WebServices.Data.Task") -as "Type"  
    4. $methodInf = $AtColtype.GetMethod("AddItemAttachment");  
    5. $AddItmAttachMethod = $methodInf.MakeGenericMethod($Tasktype);  
    6. $TaskAttachment = $AddItmAttachMethod.Invoke($mail.Attachments,$null);  
    7. $TaskAttachment.Item.Subject = "Mondays Task"  
    8. $TaskAttachment.Item.StartDate = (Get-Date)  
    9. $TaskAttachment.Item.DueDate = (Get-Date).AddDays(7)  
    10. $TaskAttachment.Name = "Mondays Task"  
    11. $mail.Subject = "See the Attached Task"  
    12. $mail.ToRecipients.Add("user@domain.com")   
    13. $mail.SendAndSaveCopy()   


    Forwarding a Message as an Attachment using the EWS Managed API and Powershell

    $
    0
    0
    While you can easily create an Inbox rule to forward a Message as an Attachment, doing this programmatically in EWS isn't that straight forward. As there is no direct method in EWS to attach an existing Store item a work around is needed.

    With this workaround we use the MimeContent of an existing email message which will allow you to create an ItemAttachment of this existing message (using the method from this post) in the Inbox and maintain all the Mime Properties and attachments on the message your forwarding. Note this method doesn't maintain full fidelity of a Message as it won't include any MAPI properties or Exchange rich datatypes which may or may not be important.

    The following sample demonstrates how to forward the more recently received email in the Inbox as an Item Attachment using the MimeContent (and also setting the MessageFlags property to make sure the message appears sent). 
    1. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
    2. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
    3. #Define ItemView to retrive just 1 Item     
    4. $ivItemView =  New-Object Microsoft.Exchange.WebServices.Data.ItemView(1)      
    5. $fiItems = $service.FindItems($Inbox.Id,$ivItemView)   
    6. if($fiItems.Items.Count -eq 1){  
    7.     $mail = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)   
    8.     $OriginalEmail = $fiItems.Items[0]  
    9.     $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly)    
    10.     $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::MimeContent)  
    11.     $psPropset.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject)  
    12.     $OriginalEmail.Load($psPropset)  
    13.     $AtColtype = ("Microsoft.Exchange.WebServices.Data.AttachmentCollection") -as "Type"  
    14.     $Emailtype = ("Microsoft.Exchange.WebServices.Data.EmailMessage") -as "Type"  
    15.     $methodInf = $AtColtype.GetMethod("AddItemAttachment");  
    16.     $AddItmAttachMethod = $methodInf.MakeGenericMethod($Emailtype);  
    17.     $EmailAttachment = $AddItmAttachMethod.Invoke($mail.Attachments,$null);  
    18.     $EmailAttachment.Item.MimeContent = $OriginalEmail.MimeContent  
    19.     $PR_Flags = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3591, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);    
    20.     $EmailAttachment.Item.SetExtendedProperty($PR_Flags,"1")    
    21.     $EmailAttachment.Name = $OriginalEmail.Subject  
    22.     $mail.Subject = "See the Attached Email"  
    23.     $mail.ToRecipients.Add("glen.scales@messageops.com")   
    24.     $mail.SendAndSaveCopy()     
    25. }  


    Viewing all 241 articles
    Browse latest View live


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