Implementing Outlook 2010's group by conversation using EWS and Exchange 2007 - c#

We're using EWS to generate some analytics on some of our mailboxes.
Part of this is getting a count/name/start/end of conversations. A conversation being analogous to the way Outlook 2010 shows them when grouping by conversation.
I was hoping to be able to use the ConversationId to group items, but that seems to be an Exchange 2010-only feature.
I can group by subject within a folder to get a simple idea of threads... however this does not handle split conversations, as Outlook 2010 does - specifically, it doesn't handle bringing in the replies that are in the sent items (these are important to us - we can't get good metrics without also looking at replies).
My current code for getting thread info looks like this:
private IEnumerable<EmailThread> GetThreads(Folder folder)
{
var view = new ItemView(int.MaxValue) {PropertySet = new PropertySet(BasePropertySet.IdOnly)};
// view.PropertySet.Add(ItemSchema.ConversationId); - Can't use this as we're stuck on Exchange 2007 !!!
view.PropertySet.Add(ItemSchema.Subject);
view.PropertySet.Add(ItemSchema.DateTimeReceived);
var grouping = new Grouping(ItemSchema.Subject, SortDirection.Descending, ItemSchema.DateTimeReceived, AggregateType.Maximum);
var groupResults = folder.FindItems(view, grouping);
return groupResults.Select(x => new EmailThread
{
Name = x.Items.First().Subject,
Items = x.Items.Count,
StartDate = x.Items.Last().DateTimeReceived, // Assume last in thread is first email
EndDate = x.Items.First().DateTimeReceived // Assume first in thread is most recent
});
}
I am hoping someone knows of a neat way to efficiently get information on replies that constitute part of a conversation?

You can fetch the ConversationId and the ConversationIndex via extended properties:
private static readonly ExtendedPropertyDefinition ConversationIdProperty = new ExtendedPropertyDefinition(0x3013, MapiPropertyType.Binary);
private static readonly ExtendedPropertyDefinition ConversationIndexProperty = new ExtendedPropertyDefinition(0x0071, MapiPropertyType.Binary);
var items = service.FindItems(WellKnownFolderName.Inbox, new ItemView(512) { PropertySet = new PropertySet(BasePropertySet.FirstClassProperties,
ConversationIdProperty, ConversationIndexProperty)});
Both are binary properties. Their content is described in great detail here:
[MS-OXOMSG]: E-Mail Object Protocol Specification, section 2.2.1.2 and 2.2.1.3.
The properties themselves are defined in [MS-OXPROPS]: Exchange Server Protocols Master Property List.

Related

EWS Extended Properties between accounts

we have encountered a problem with recognizing and setting ExtendedProperties by EWS. Right now, we have the functionality to import emails, which can be done either manually by user or a separate service. There seems to be inconsistency with how Exchange searches through ExtendedProperties and how they are set.
To clarify, I have three mailboxes attached to Outlook application. One is mine and two are the test accounts, which are linked to my AD account. The test accounts contain emails which are imported by the automatic service(when a mail is sent to one of them, it is imported to our application and there is set and ExtendedProperty on the email, providing the information that the email was imported).
What is strange, on my side, in Outlook Add-in, on one account the emails are properly marked and on the other it is not happening as it should, despite that the mechanism is the same.
These are the codes of our add-in.
This is the code of retrieving emails:
private static IEnumerable<EmailMessage> GetEmails(Microsoft.Exchange.WebServices.Data.Folder folder, int pageSize, DateTime lastImport)
{
SearchFilter filter = PrepareFilter(lastImport);
List<Item> foundItems = new List<Item>();
Guid propertySetId = new Guid("F723C954-3F83-46AA-A783-FDEAC90AE512");
ExtendedPropertyDefinition registered = new ExtendedPropertyDefinition(propertySetId, "Registered", MapiPropertyType.Boolean);
ItemView view = new ItemView(pageSize);
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties, registered);
foundItems = service.FindItems(folder.Id, filter, view).Items.ToList();
List<EmailMessage> results = new List<EmailMessage>();
foreach (EmailMessage message in foundItems)
{
results.Add(message);
}
return results;
}
private static SearchFilter PrepareFilter(DateTime lastImport)
{
Guid propertySetId = new Guid("F723C954-3F83-46AA-A783-FDEAC90AE512");
ExtendedPropertyDefinition Registered= new ExtendedPropertyDefinition(propertySetId, "Registered", MapiPropertyType.Boolean);
SearchFilter isRegistered = new SearchFilter.IsEqualTo(registered, true);
return isRegistered;
}
And this is the flag setting method written by my colleague:
public static bool SetExchangeRegistered(List<MailItem> mailsToRegister)
{
try
{
var service = new Exchange.ExchangeService(Exchange.ExchangeVersion.Exchange2010)
{
UseDefaultCredentials = true
};
var emailAddress = new Application().ActiveExplorer().Session.CurrentUser.AddressEntry.GetExchangeUser().PrimarySmtpAddress;
service.AutodiscoverUrl(emailAddress);
foreach (var mail in mailsToRegister)
{
string itemId = ConvertHexEntryIdToEwsId(service, mail.EntryID, emailAddress);
Exchange.EmailMessage message = Exchange.EmailMessage.Bind(service, new Exchange.ItemId(itemId));
bool propertyRegisteredExists =
message.ExtendedProperties.FirstOrDefault(x => x.PropertyDefinition.Name == "Registered") != null;
if (!propertyRegisteredExists)
{
Guid propertySetId = new Guid("F723C954-3F83-46AA-A783-FDEAC90AE512");
Exchange.ExtendedPropertyDefinition registered =
new Exchange.ExtendedPropertyDefinition(propertySetId, "Re", Exchange.MapiPropertyType.Boolean);
message.SetExtendedProperty(registered, true);
}
else
{
message.ExtendedProperties.First(x => x.PropertyDefinition.Name == "Registered").Value = true;
}
message.Update(Exchange.ConflictResolutionMode.AlwaysOverwrite);
}
}
catch (Exception ex)
{
grmtAddInBase.Logger.Trace(string.Format("Registered update failed: {0}", ex.Message));
return false;
}
grmtAddInBase.Logger.Trace("Email property 'Registered' updated successfully");
return true;
}
Generally, the method above seems to be identical in control flow to the method which is implemented in the separate service, and it seems that it actually does set the ExtendedProperty of the email correctly.
Another clue, which, to be honest, leaves me clueless, is that even when I tried to get all the emails greedily, load it and then separate the correct ones by their ExtendedProperty which we set... The problem then is that for some emails(i.e. inside one of the mailboxes which are treated by the autoimport service) it sees the Properties correctly and for the rest(i.e. my own mailbox) it doesn't even load and ExtProps, which means that it probably does not see them at all.
I have also tried to use DefaultExtendedPropertySet.PublicStrings, but then it didn't work at all.
I am a little bit puzzled and nobody in close proximity or EWS docs/MS Forums could provide the answer. I am aware that there may be no help, I am aware of the possibility that we are just hopelessly stupid and made some mistake which we cannot find.
After a while, the emails on Exchange mailboxes are just copies - that would be reasonable reason why we cannot access the ExtendedProperties of the email that we received, when it's "flag" was set by another user. But, maybe there is a way to synchronize those properties between those mailboxes? Because there are some alternatives already that we will discuss, but it would be nice if at least part of the current solution could be reused.

How to get all installations when using Azure Notification Hubs installation model?

Using NotificationHubClient I can get all registered devices using GetAllRegistrationsAsync(). But if I do not use the registration model but the installation model instead, how can I get all installations? There are methods to retrieve a specific installation but none to get everything.
You're correct, as of July 2016 there's no way to get all installations for a hub. In the future, the product team is planning to add this feature to the installations model, but it will work in a different way. Instead of making it a runtime operation, you'll provide your storage connection string and you'll get a blob with everything associated with the hub.
Sorry for visiting an old thread... but in theory you could use the GetAllRegistrationsAsyc to get all the installations. I guess this will return everything without an installation id as well, but you could just ignore those if you choose.
Could look something like this
var allRegistrations = await _hub.GetAllRegistrationsAsync(0);
var continuationToken = allRegistrations.ContinuationToken;
var registrationDescriptionsList = new List<RegistrationDescription>(allRegistrations);
while (!string.IsNullOrWhiteSpace(continuationToken))
{
var otherRegistrations = await _hub.GetAllRegistrationsAsync(continuationToken, 0);
registrationDescriptionsList.AddRange(otherRegistrations);
continuationToken = otherRegistrations.ContinuationToken;
}
// Put into DeviceInstallation object
var deviceInstallationList = new List<DeviceInstallation>();
foreach (var registration in registrationDescriptionsList)
{
var deviceInstallation = new DeviceInstallation();
var tags = registration.Tags;
foreach(var tag in tags)
{
if (tag.Contains("InstallationId:"))
{
deviceInstallation.InstallationId = new Guid(tag.Substring(tag.IndexOf(":")+1));
}
}
deviceInstallation.PushHandle = registration.PnsHandle;
deviceInstallation.Tags = new List<string>(registration.Tags);
deviceInstallationList.Add(deviceInstallation);
}
I am not suggesting this to be the cleanest chunk of code written, but it does the trick for us. We only use this for debugging type purposes anyways

C# google contact api deleted contact

currently I´m writing on a outlook plugin for syncing goolge contacts with outlook but I have to cover some special case:
When a contact gets deleted on google side, my application detects the missing contact and creates a new contact based on the contact info from the outlook one.
Is there a way to get an event or history from google that tells me a user deleted this contact(s)?
Edit 1:
Here is my code how I´m accessing the contacts (what is working FINE):
public GoogleAccessor()
{
var parameters = new OAuth2Parameters()
{
ClientId = CLIENTID,
ClientSecret = CLIENTSECRET,
RedirectUri = REDIRECTURI,
Scope = SCOPES
};
string url = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
//An own webbrowser for processing the access tokens
IAuthorizationCodeProvider authcodeProvider = new Presentation.BrowserAuthorizationCodeProvider(new Presentation.BrowserAuthentificatorVM());
parameters.AccessCode = authcodeProvider.GetAuthorizationCode(url);
if(parameters.AccessCode == null)
throw new GoogleOAuthException("AccesCode returned 'null' and failed!");
OAuthUtil.GetAccessToken(parameters);
this._contactsRequest = new ContactsRequest(new RequestSettings(APPLICATIONNAME, parameters) {AutoPaging = true});
}
public IList<IContact> GetAllMappedContacts()
{
Feed<Google.Contacts.Contact> f = _contactsRequest.GetContacts();
this._feedUri = new Uri(f.AtomFeed.Feed);
var photoList = new List<PhotoObject>();
foreach (var entry in f.Entries)
{
var photoObject = GetContactPhoto(entry);
if(photoObject != null)
photoList.Add(photoObject);
}
_googleMapper = new GoogleMapper(f.Entries);
return _googleMapper.MapToLocalContacts();;
}
The thing about syncing in general is that syncing is normally meant to work in one direction.
Source Data -> Data Flow -> Received Data.
In this instance, Outlook is your source data and Google is your received data. All information needs to come from your source. Since this is an Outlook add-in you are creating my suggestion would be to add a button to your add-in ribbon. You can call the button whatever ever you like (maybe "dontSyncButton"), but it's purpose is going to be Categorization of your contact.
Make it so that that when a contact is selected and then the button is clicked, the contact is given a special categorization (perhaps "Dont Sync").
Now give some logic to your code that executes the sync, and have that logic decide whether to sync the contact. Also, give some logic to tell the program to delete the contact out of Google for you if the contacts contains the special category. Semi-Pseudo Code below:
if(contact.Categories.ToString() == "Dont Sync")
{
//Don't Sync Contact
If(googleContact.Exists())
{
//Delete contact from Google if it exist
googleContact.Delete();
}
}
else
{
//Sync Contact
}
It would be nice if Outlook had many modifiable properties that weren't visible to users, but since it does not this is really one of the best options I can think of. I do this to sync contacts from a shared Outlook folder to personal ones and it has worked well so far.
Hope this helps!

How to get all the users in Zendesk

I have created users using CreateOrUpdateUser() method but i was unable to fetch all the users from zendesk. I am getting null for "oListUser" also I tried to fetch user list for Organization but for that also i am getting null.Any help would be appreciated. There is no issue with the connection.
Code:
ZenDeskApi.ZenDeskApi oZen = new ZenDeskApi.ZenDeskApi("https://waresolution.zendesk.com", "j#se.com", "87ggh76IO");
List<User> oListUser = oZen.GetUsers();
User oUsers = new ZenDeskApi.Model.User();
oUsers.Email = "r#se.com";
oUsers.IsVerified = true;
oUsers.Name = "R r";
oUsers..........// Other properties
int a = oZen.CreateOrUpdateUser(oUsers);
List<Organization> oOrg = oZen.GetOgranizations();
foreach (var orgItem in oOrg)
{
int orgId = orgItem.Id;
}
Take a look at this zendesk api client for C#.net on Github. See the JUSTEAT blog for more details. You can use this client to get all users like this:
Create a client:
IZendeskClient client = new ZendeskClient(
new Uri("my-zendesk-api-host-endpoint"),
"my-zendesk-username",
"my-zendesk-token"
);
Then you can use the Search resource to search all users:
var result = client.Search.Find(new ZendeskQuery<User>().WithCustomFilter("y", "x"));
You can download the code as a Nuget here
I am using ZendeskApi_v2 and I am able to get all the users using api as follows:
var userList = api.Users.GetAllUsers();
Here userList is GroupUserReponse.I doubt whether we have any method GetUsers().Atleast its not available in the version which I am using.Once you get the response you can iterate through it.
I see that this question is related to ZenDeskApi which is available at this location:
https://github.com/eneifert/ZenDeskApi.
Sorry,I have not worked on it and tried this.
Not sure if you got a response to this but for anyone else looking for info on the API:
Try instead of:
ZenDeskApi.ZenDeskApi oZen = new ZenDeskApi.ZenDeskApi("https://waresolution.zendesk.com", "j#se.com", "87ggh76IO");
ZenDeskApi.ZenDeskApi oZen = new ZenDeskApi("https://waresolution.zendesk.com", "j#se.com", "87ggh76IO");
Also, to get a paginated list of 100 users instead of the group response, just call:
var zendeskUsers = oZen.Users.GetAllUsers().Users;

EWS - how do I find all incomplete tasks?

I am using Exchange Web Services to try to get a list of all Outlook tasks which are not complete.
I have an instance of ExchangeService, and attempt to find all incomplete tasks like this:
SearchFilter searchFilter = new SearchFilter.IsNotEqualTo(TaskSchema.Status, TaskStatus.NotStarted);
FindItemsResults<Item> tasks = service.FindItems(WellKnownFolderName.Tasks, searchFilter, view);
However, on the last line, I get a "ServiceResponseException: The specified value is invalid for property." This seems weird to me because the EWS documentation explicitly states that the Task.Status is supposed to be one of the TaskStatus enumeration values. Creating a SearchFilter which compares against a string value does not cause an exception, but I haven't tried any of the other enumeration options to see whether they give the same behavior.
I am able to do this using ExtendedPropertyDefinition with Exchange 2007.
I am using PidLidTaskComplete Canonical Property.
Full list of named properties available here.
//Create the extended property definition.
ExtendedPropertyDefinition taskCompleteProp = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Task, 0x0000811C, MapiPropertyType.Boolean);
//Create the search filter.
SearchFilter.IsEqualTo filter = new SearchFilter.IsEqualTo(taskCompleteProp, false);
//Get the tasks.
FindItemsResults<Item> tasks = service.FindItems(WellKnownFolderName.Tasks, filter, new ItemView(50));
I believe you may also achieve that without using any magic numbers:
var view = new ItemView(20);
var query = new SearchFilter.IsNotEqualTo(TaskSchema.IsComplete, true);
var results = exchangeService.FindItems(WellKnownFolderName.Tasks, query, view);
This does work on a certain version of exchange :)

Categories

Resources