Showing posts with label Exchange Web Services. Show all posts
Showing posts with label Exchange Web Services. Show all posts

Thursday, 30 January 2014

SCSM: Changing the Message Class of Emails for Ingestion

Scenario

We have deployed SCSM (System Centre Service Manager), and setup a mailbox for receiving and processing service requests. We also have a custom Outlook com-addin installed on all workstations. The addin ensures email is filed to our DMS (Document Management System). When the addin processes email, it changes the message class of the email from IPM.Note to a one of four custom message classes. For example, email that is filed to the DMS, gets the new message class IPM.Note.Imc.Dms.Filed.

This causes a problem with the SCSM ingestion process. When someone emails a service request to the SCSM mailbox, if they file the message, the message class will be changed. The SCSM ingestion process only processes emails that have a message class of IPM.Note. This results in emails that don't get processed by SCSM, and service requests that "are lost".

Solution

Have something runs on a schedule and updates the message class of all emails in the SCSM mailbox that have a custom message class. Sounds simple right? Well, using PowerShell and EWS (Exchange Web Services), it actually is!

The script below demonstrates using EWS to search the inbox of a mailbox, looking for emails that contain a specific value in the message class. Once the search has finished, the script iterates through the collection of items, changing the message class back to IPM.Note.

To work with EWS and PowerShell, you'll need to meet the following requirements
And here's the PowerShell... to the rescue again!

#Get the SCSM Mailbox            
$Identity = Get-Mailbox SCSM             
#Load the Exchange Web Services DLL            
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll";                        
[void][Reflection.Assembly]::LoadFile($dllpath);                        
#Create a service reference            
$Service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1);                        
$mailAddress = $Identity.PrimarySmtpAddress.ToString();                        
$Service.AutodiscoverUrl($mailAddress);                        
$enumSmtpAddress = [Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress                        
#Set Impersonation            
$Service.ImpersonatedUserId =  New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId($enumSmtpAddress,$mailAddress);             
#Limit the page size. If there are more than 100 results returned from the search, we'll page through them            
$pageSize=100;                        
$pageLimitOffset=0;                        
$getMoreItems=$true;                        
$itemCount=0;                        
#These are the properties that we want returned with our search results            
$propItemSubject = [Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject;            
$propItemClass = [Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass;                     
$itemsProcessed = 0;            
                        
while ($getMoreItems)                        
{                           
 #Create a view that includes the page size and page offset            
 $view = new-object Microsoft.Exchange.WebServices.Data.ItemView($pageSize,$pageLimitOffset,[Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning);                        
 $view.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Shallow;                         
 #Add the two properties to the view            
 $view.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet($propItemClass,$propItemSubject);                         
 #Define the first filter. This filter is used to look for any email with a message class that contains "IMC" and is used to find emails containing our custom message class            
 $searchFilterItemClass1 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring($propItemClass,"IMC",[Microsoft.Exchange.WebServices.Data.ContainmentMode]::Substring,[Microsoft.Exchange.WebServices.Data.ComparisonMode]::IgnoreCase);            
 #Define the first filter. This filter is used to look for any email with a message class that contains "OofTemplate", and is used to find Out Of Office emails            
 $searchFilterItemClass2 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring($propItemClass,"OofTemplate",[Microsoft.Exchange.WebServices.Data.ContainmentMode]::Substring,[Microsoft.Exchange.WebServices.Data.ComparisonMode]::IgnoreCase);            
 #Create a search filter collection            
 $searchFilters = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::Or);                        
 #Add the two search filters to the collection            
 $searchFilters.add($searchFilterItemClass1);                        
 $searchFilters.add($searchFilterItemClass2);             
 #Perform the search            
 $mailItems = $Service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$searchFilters,$view);                         
 #Iterate through the emails, updating the message class            
 foreach ($item in $mailItems.Items)                        
 {                        
  if ($item.GetType().FullName -eq "Microsoft.Exchange.WebServices.Data.EmailMessage")                        
  {                            
   Write-Host ([String]::Format("************** {0} ******************",$item.Subject));                        
   Write-Host "Message Class:"$item.ItemClass;            
   $item.ItemClass = "IPM.Note";            
   $item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite);            
   Write-Host ([String]::Format("Item updated.",$item.Subject));             
   $itemsProcessed++;            
  }                        
 }            
 #Check if there are more emails available in the mailbox that match our search criteria            
 if ($mailItems.MoreAvailable -eq $false){$getMoreItems = $false}            
 #If there are more items to get, update the page offset            
 if ($getMoreItems){$pageLimitOffset += $pageSize}                         
}            
Write-Host ([String]::Format("Updated the message class on {0} mail items.",$itemsProcessed));             

References


Wednesday, 4 December 2013

Access Mailbox Contacts with PowerShell and EWS (Exchange Web Services)

Introduction

This post deals with using PowerShell,  EWS (Exchange Web Services) and Impersonation, to get contact information for a collection of users. The example in this article demonstrates how to get contacts from a single users mailbox, while the downloadable code contains an example using functions and collections of custom objects used to store contact information to produce reports.

The basis for this code was a requirement a report of all mailbox contacts with two or more email addresses. I wrote a script that can be used to query mailboxes and recursively check all Contact folders for contacts with more than one email address. That script can be downloaded from the TechNet Gallery, here: http://gallery.technet.microsoft.com/Get-Contacts-from-Exchange-88efa647.

Otherwise, if you're just looking for an example of using PowerShell and EWS, check out the Example section below.

Example - Searching an Exchange Mailbox for all Contacts with more than one Email Address.

To work with EWS and PowerShell, you'll need to meet the following requirements

$Identity = Get-Mailbox ima.plonker@company.com            
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll";            
[void][Reflection.Assembly]::LoadFile($dllpath);            
$Service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1);            
$mailAddress = $Identity.PrimarySmtpAddress.ToString();            
$Service.AutodiscoverUrl($mailAddress);            
$enumSmtpAddress = [Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress            
$Service.ImpersonatedUserId =  New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId($enumSmtpAddress,$mailAddress);            
            
$pageSize=100;            
$pageLimitOffset=0;            
$getMoreItems=$true;            
$itemCount=0;            
$propGivenName = [Microsoft.Exchange.WebServices.Data.ContactSchema]::GivenName;            
$propSurname = [Microsoft.Exchange.WebServices.Data.ContactSchema]::Surname;            
$propEmail1 = [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress1;            
$propEmail2 = [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress2;            
$propEmail3 = [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress3;            
$propDisplayName = [Microsoft.Exchange.WebServices.Data.ContactSchema]::DisplayName;            
            
while ($getMoreItems)            
{               
 $view = new-object Microsoft.Exchange.WebServices.Data.ItemView($pageSize,$pageLimitOffset,[Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning);            
 $view.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Shallow;            
 #Added properties to be returned with the query results            
 $view.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet($propGivenName,$propSurname,$propEmail1,$propEmail2,$propEmail3,$propDisplayName);             
 #Added three filter properties for the contacts Email fields (there are three of them).            
 $searchFilterEmail1 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+Exists($propEmail1);            
 $searchFilterEmail2 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+Exists($propEmail2);            
 $searchFilterEmail3 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+Exists($propEmail3);            
 #Add the filter objects to the filters collection using the OR operator            
 $searchFilters = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::Or);            
 $searchFilters.add($searchFilterEmail1);            
 $searchFilters.add($searchFilterEmail2);            
 $searchFilters.add($searchFilterEmail3);             
 #Perform the search against the default Contacts folder, and store the results in a variable            
 $contactItems = $Service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$searchFilters,$view);            
 #Foreach contact, print the contacts display name and email addresses.            
 foreach ($item in $contactItems.Items)            
 {            
  if ($item.GetType().FullName -eq "Microsoft.Exchange.WebServices.Data.Contact")            
  {                
   Write-Host ([String]::Format("************** {0} ******************",$item.DisplayName));            
   Write-Host "First Name:"$item.GivenName;            
   Write-Host "Surname:"$item.Surname;               
   Write-Host "Email 1:"($item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1]).Address;            
   Write-Host "Email 2:"($item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress2]).Address;            
   Write-Host "Email 2:"($item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress3]).Address;               
  }            
 }            
 if ($contactItems.MoreAvailable -eq $false){$getMoreItems = $false}            
 if ($getMoreItems){$pageLimitOffset += $pageSize}             
}


Thursday, 26 July 2012

Adding appointments to an Outlook calendar using EWS (Exchange Web Services) in a Webpart

Recently I had a requirement to create a solution for tracking and scheduling internal events. As part of this, I wanted the solution to add and remove appointments from users Outlook calendars automatically upon registration / cancellation.

Exchange Web Services was the answer.

My approach was:

1. Generate "EWS.dll" (I named mine IEWS.dll)
2. Create a SharePoint solution that registers IEWS.dll as a safe control in the farm and contains an application page for managing my custom Exchange settings (usernames, urls, etc)
3. Create a second solution that contains the webparts, content types, etc, that my solution requires. This solution will have a reference to IEWS.dll, and feature dependency on the first solution.

Step 1.

Everyone seems to refer to EWS.dll on the Internet. The fact is, you need to generate it yourself, and you can call it whatever you like. There's a MSDN blog about it here.

The first step is creating a class file from the Exchange Web Services web service. I used the Microsoft wsdl.exe tool shipped with Visual Studio to do this. All you need to do is specify the language you want to use, the location the class file will be output to, the namespace you want to use (you specify whatever you want this to be) and the URL to an Exchange Web Services server (any of your Exchange servers running the Client Access role).

wsdl.exe /language:cs /out:c:\temp\IEws.cs /namespace:ExchangeWebServices https://myexchangeserver/ews/services.wsdl

Next, we need to compile the class and sign it, using the .Net framework 3.5. There are various ways to do this. What I did was: 

1. Create a new Visual Studio project (I named it IEWS - The "I" in IEWS is the prefix I used to denote our company )

2. Add a new class file to the project (I called mine ExchangeWebServices.cs, to match the class file name I created using the wsdl tool)

3. Delete the contents of the class file

4. Copy the contents of the class file you created with wsdl.exe (c:\temp\IEws.cs) into your new class file




5. Next I added some additional methods to handle my requirements. I added a new class called Extensions, and added my helper methods in there (adding and removing calendar entries, among others). You'll need to add two using statements, one for System.Web.Services, another for the ExchangeWebServices namespace, as well as any others you need, like Microsoft.SharePoint)

One of the my custom (overloaded) methods that adds a calendar appointment to a users mailbox looks like this:

public static Boolean CreateAppointment(string usersEmail, string subject, string description, string location, DateTime startTime, DateTime endTime, Guid appointmentUid, Credentials credentials, String exEwsUrl, out String messages)
{
  StringBuilder output = new StringBuilder();
  try
  {
    ExchangeServiceBinding esb = new ExchangeServiceBinding();
    esb.Credentials = new NetworkCredential(credentials.Username, credentials.Password.ConvertToUnsecureString(), credentials.Domain);
    esb.Url = exEwsUrl;
    esb.RequestServerVersionValue = new RequestServerVersion();
    esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1;

    //Setup Impersonation
    esb.ExchangeImpersonation = new ExchangeImpersonationType();
    esb.ExchangeImpersonation.ConnectingSID = new ConnectingSIDType();
    esb.ExchangeImpersonation.ConnectingSID.PrimarySmtpAddress = usersEmail;


    // Create the request.
    AddDelegateType request = new AddDelegateType();
    //ToDp: handle the certificate check better.
    ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;

    // Create the appointment.
    CalendarItemType appointment = new CalendarItemType();

    // Add item properties to the appointment.
    appointment.Body = new BodyType();
    appointment.Body.BodyType1 = BodyTypeType.HTML;
    appointment.Body.Value = CreateHtmlBody(subject, description, location, startTime, endTime);
    appointment.Importance = ImportanceChoicesType.High;
    appointment.ImportanceSpecified = true;
    appointment.ItemClass = "IPM.Appointment";
    appointment.Subject = subject;
    appointment.Location = location;
    appointment.UID = appointmentUid.ToString();
    ExtendedPropertyType[] eProps = CreateAppointmentUidProperty(String.Format("{0}", appointmentUid));
    appointment.ExtendedProperty = eProps;

    // Add calendar properties to the appointment.
    var timeUtc = startTime.ToUniversalTime();
    var endTimeUtrc = endTime.ToUniversalTime();
    appointment.Start = timeUtc;
    appointment.StartSpecified = true;
    appointment.End = endTimeUtrc;
    appointment.EndSpecified = true;
    appointment.ReminderMinutesBeforeStart = "30";
    appointment.ReminderIsSet = true;

    
    // Identify the destination folder that will contain the appointment.
    var folder = new DistinguishedFolderIdType();
    folder.Id = DistinguishedFolderIdNameType.calendar;
    
    // Create the array of items that will contain the appointment.
    var arrayOfItems = new NonEmptyArrayOfAllItemsType();
    arrayOfItems.Items = new ItemType[1];

    // Add the appointment to the array of items.
    arrayOfItems.Items[0] = appointment;

    // Create the CreateItem request.
    CreateItemType createItemRequest = new CreateItemType();

    // The SendMeetingInvitations attribute is required for calendar items.
    createItemRequest.SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType.SendToNone;
    createItemRequest.SendMeetingInvitationsSpecified = true;

    // Add the destination folder to the CreateItem request.
    createItemRequest.SavedItemFolderId = new TargetFolderIdType();
    createItemRequest.SavedItemFolderId.Item = folder;

    // Add the items to the CreateItem request.
    createItemRequest.Items = arrayOfItems;

    // Send the request and get the response.
    CreateItemResponseType createItemResponse = esb.CreateItem(createItemRequest);

    ArrayOfResponseMessagesType responseMessages = createItemResponse.ResponseMessages;
    ResponseMessageType findResponseType = responseMessages.Items[0];
    if (findResponseType.ResponseClass != ResponseClassType.Success)
    {
      output.Append(String.Format("Failed to create item. Response Class: {0}. Response Messages: {1}", findResponseType.ResponseClass, findResponseType.MessageText));
      return false;
    }

    // Get the response messages.
    ResponseMessageType[] rmta = createItemResponse.ResponseMessages.Items;
    output.Append(String.Format("<div>An appointment has been added to your calendar.</div>"));
    output.Append(String.Format("<div>Repsonse:</div>"));
    foreach (ResponseMessageType rmt in rmta)
    {
      ArrayOfRealItemsType itemArray = ((ItemInfoResponseMessageType)rmt).Items;
      ItemType[] items = itemArray.Items;
      // Get the item identifier and change key for each item.
      foreach (ItemType item in items)
      {
        output.Append(String.Format("<div>Item identifier: {0}</div>", item.ItemId.Id));
        output.Append(String.Format("<div>Item change key: {0}</div>", item.ItemId.ChangeKey));
      }
    }
    return true;
  }
  catch (Exception e)
  {
    output.Append(e.Message);
    return false;
  }
  finally
  {
    messages = output.ToString();
  }
}

private static ExtendedPropertyType[] CreateAppointmentUidProperty(String value)
{
  PathToExtendedFieldType pathToAppointmentUid = new PathToExtendedFieldType();
  pathToAppointmentUid.DistinguishedPropertySetId = DistinguishedPropertySetType.PublicStrings;
  pathToAppointmentUid.DistinguishedPropertySetIdSpecified = true;
  pathToAppointmentUid.PropertyName = CalendarTrackingUidName;
  pathToAppointmentUid.PropertyType = MapiPropertyTypeType.String;

  ExtendedPropertyType appointmentPropertyUid = new ExtendedPropertyType();
  appointmentPropertyUid.ExtendedFieldURI = pathToAppointmentUid;
  appointmentPropertyUid.Item = value;

  ExtendedPropertyType[] eProps = new ExtendedPropertyType[1];
  eProps[0] = appointmentPropertyUid;
  return eProps;
} 

6. Once all the extension methods are finished, the next step is to sign the project and build it. This will, among other things, compile the code as IEWS.dll, which I can now use in my next project.

Step 2.

1. The next step I took, was creating a new empty SharePoint project (in the same solution). This project will be responsible for deploying the IEWS.dll to the SharePoint farm (so that other solutions can use it), and will install an application page I can use to configure settings the extension methods I created will need (usernames, passwords, URLs, etc).

To have the solution deploy IEWS.dll as an additional assembly when the solution is deployed;

1. Open the Package manager
2. Click on Advanced


3. Click Add > Add Assembly from Project Output...
4. For the Source Project, I selected the first project I created, IEWS, which contains the Exchange Web Services class and the extension class I created in Step 1.



5. Under the safe controls section, click on Click here to add new item
6. Add the ExchangeWebServices namespace


7. Click OK.
8. Build the solution and deploy it.

Step 3 - Make use of the Exchange Web Services in a webpart.

This is the easy bit!

1. Create a new solution, and add an empty SharePoint project

2. Add a reference to IEWS.dll created in the previous solution (note that this dll doesn't need to be packaged with this solution - the first solution is responsible for deploying it to your SharePoint servers)

3. Add a webpart to the project
4. Add all the controls you need to the webpart. Obviously this will depend on what you're doing. Mine looks like this;

4a. The webpart displays a list of available events a user can register for.

4b. When the user selects an event to register for, an application page is displayed in the SharePoint dialog framework to allow the user to register themselves or select someone else they are registering on behalf of.




5. When the user clicks Register, code adds the event to the specified users calendar using methods in my IEWS. The code in the application page looks a bit like this (I've slimmed it down to essentials)

using System;
using System.Collections;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using IEWS;

namespace Ince.Events.Layouts.Ince.Events
{

public partial class bookevent : LayoutsPageBase
{
private Guid _listId;
private Guid _webId;
private int _itemId;

protected void Page_Load(object sender, EventArgs e){...}        

private void PopulateFields(Guid webid, Guid listid, int itemid){...}        

protected void SubmitClick(Object sender, EventArgs e)
{
try
{
(code to get page arguments)
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using(SPSite site = new SPSite(SPContext.Current.Site.Url))
{
site.AllowUnsafeUpdates = true;

using (SPWeb web = site.OpenWeb(webid))
{
try
{
web.AllowUnsafeUpdates = true;

(code omitted that gets the events list, gets the selected event, checks if there are available spaces, adds the selected user as an attendee and updates the list item)                                                                

//Add the event to the selected users mailbox
if (IEWS.ExchangeServices.CreateAppointment(usersemailaddress, item["Title"].ToString(), description.ToString(), locationAsText.ToString(), startDate, endDate, item.UniqueId))
{
eventConfirmationMessage.Text = String.Format("<div>You've been registered for this event, and we've added an appointment to your Outlook calendar to help remind you.</div>");
}
else
{
eventConfirmationMessage.Text = String.Format("<div>You've been registered for this event, but we failed to add an appointment to your Outlook calendar. Please make a note of the time and date, and optionally manually add the appointment to your Outlook calendar.</div>");
}
(code omitted that handles exceptions, etc)
...
}
6. Deploy the solution.