I'm trying to find a way to list ALL extended properties for set of calendar items using EWS.
The problem is that all examples I managed to find online require me to know what those extended properties are, in advance. Here's the official MSDN example.
What am I supposed to do if I do not know the IDs or names of extended properties? Or if I don't even know if any extended properties exist at all?
I've tried the following code but it returns an exception...
var calendarItems = service.FindAppointments(WellKnownFolderName.Calendar, view);
var propertySet = new PropertySet(AppointmentSchema.ExtendedProperties);
service.LoadPropertiesForItems(calendarItems, propertySet);
Here's the exception:
Microsoft.Exchange.WebServices.Data.ServiceResponseException: The request failed schema validation: The required attribute 'FieldURI' is missing.
There is no call in EWS to get all extended properties. The idea behind extended properties is that applications use them to store app-specific data, so only that app needs to know the specifics on their properties.
Extended MAPI can discover this information. https://github.com/stephenegriffin/mfcmapi has a ton of sample code for different tasks, including iterating named properties.
I was also looking similar and I just did a kind of reverse engineering. Since the extended property is the combination of Id (integer) and data type which we could not know as they are not documented on any MSDN. So, iterate 1 to some huge number like 15000 for string type property and find those which can be loaded successfully - this is the main trickest part which we can do by putting try-catch to bind that extended property. Then you could get the required one.
Hope that helps.
List<int> allStringIds = new List<int>();
for (int i = 0; i <= 15000; i++)
{
allStringIds.Add(i);
}
ParallelOptions options = new ParallelOptions
{
MaxDegreeOfParallelism = 200,
CancellationToken = CancellationToken.None,
};
Parallel.For(0, allStringIds.Count, options, index =>
{
try
{
ExtendedPropertyDefinition extendedPropertyDefinition = new ExtendedPropertyDefinition(index,
MapiPropertyType.String);
latestMessage = EmailMessage.Bind(service, item.Id.UniqueId,
new PropertySet(BasePropertySet.FirstClassProperties, extendedPropertyDefinition));
_logger.Write("Supported string property id=" + index);
supportedListId.TryAdd(index, index);
}
catch(Exception ex)
{
}
});
foreach (var a in supportedListId)
{
ExtendedPropertyDefinition extendedPropertyDefinition = new ExtendedPropertyDefinition(a.Key,
MapiPropertyType.String);
allExtendedPropertyDefinitions.Add(extendedPropertyDefinition);
}
latestMessage = EmailMessage.Bind(service, item.Id.UniqueId,
new PropertySet(BasePropertySet.FirstClassProperties, allExtendedPropertyDefinitions));
foreach (var extendedProperty in latestMessage.ExtendedProperties)
{
if (extendedProperty.PropertyDefinition != null && extendedProperty.PropertyDefinition.Tag != null)
{
if (extendedProperty.Value != null)
{
_logger.Write($"OMG... extendedProperty id={extendedProperty.PropertyDefinition.Id}," +
$" name={ extendedProperty.PropertyDefinition.Name}, value={extendedProperty.Value}");
}
}
}
Related
How to get custom field value of item in Outlook Public Folder?
I need to do it in C# and preferably using ExchangeService and not Interop.
Do you have any code examples handy?
So far I am getting into desired Public Folder and can read item(s). However, I can't find any custom fields in properties.
The following example shows how to retrieve a collection of items from the Exchange mailbox, including the extended property that is identified by the GUID. The example searches each retrieved item and displays the subject line of each item and the extended property name and value, if it exists.
// Retrieve a collection of all the mail (limited to 10 items) in the Inbox. View the extended property.
ItemView view = new ItemView(10);
// Get the GUID for the property set.
Guid MyPropertySetId = new Guid("{C11FF724-AA03-4555-9952-8FA248A11C3E}");
// Create a definition for the extended property.
ExtendedPropertyDefinition extendedPropertyDefinition =
new ExtendedPropertyDefinition(MyPropertySetId, "Expiration Date", MapiPropertyType.String);
view.PropertySet =
new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject, extendedPropertyDefinition);
FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox, view);
// Search the e-mail collection for the extended property.
foreach (Item item in findResults.Items)
{
Console.WriteLine(item.Subject);
if (item.ExtendedProperties.Count > 0)
{
// Display the extended name and value of the extended property.
foreach (ExtendedProperty extendedProperty in item.ExtendedProperties)
{
Console.WriteLine(" Extended Property Name: " + extendedProperty.PropertyDefinition.Name);
Console.WriteLine(" Extended Property Value: " + extendedProperty.Value);
}
}
}
See Viewing custom extended properties by using the EWS Managed API 2.0 for more information.
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.
I have a situation where a user can upload a word document which contains placeholders for certain properties (i.e. in MS Word the user has gone to Insert > Quick Parts > Document Properties and chosen a property). The specific properties I want to support are Title, Author, Company and Publish Date.
Title and Author are set as Package Properties, and Company is set as an Extended File Property. These are set with the below code, which works correctly:
private static void SetDocumentProperties(ReportTemplateModel model, WordprocessingDocument wordDocument)
{
//these properties always exist
wordDocument.PackageProperties.Title = model.Title;
wordDocument.PackageProperties.Creator = model.Author;
wordDocument.PackageProperties.Created = DateTime.Now;
wordDocument.PackageProperties.Modified = DateTime.Now;
//these properties can be null
if (wordDocument.ExtendedFilePropertiesPart == null)
{
wordDocument.AddNewPart<ExtendedFilePropertiesPart>();
}
if (wordDocument.ExtendedFilePropertiesPart.Properties == null)
{
wordDocument.ExtendedFilePropertiesPart.Properties = new Properties(new Company(model.SiteName));
}
else
{
wordDocument.ExtendedFilePropertiesPart.Properties.Company = new Company(model.SiteName);
}
}
My problem is that I can't work out how the set the Publish Date property. I have tried adding it as a Custom File Property using the below code (which is adapted from https://www.snip2code.com/Snippet/292005/WDSetCustomProperty), but this does not work. I've read a few things about setting custom properties, but I'm confused how they're meant to work. I'm also unsure if the Publish Date should actually be set as a custom property, or some other type of property.
var customProps = wordDocument.CustomFilePropertiesPart;
if (customProps == null)
{
customProps = wordDocument.AddCustomFilePropertiesPart();
customProps.Properties = new DocumentFormat.OpenXml.CustomProperties.Properties();
}
var properties1 = new DocumentFormat.OpenXml.CustomProperties.Properties();
//I have tried both of these lines, neither worked.
//properties1.AddNamespaceDeclaration("op", "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties");
properties1.AddNamespaceDeclaration("vt", "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes");
var customDocumentProperty1 = new DocumentFormat.OpenXml.CustomProperties.CustomDocumentProperty()
{
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
PropertyId = 2,
Name = "Publish Date"
};
customDocumentProperty1.Append(new DocumentFormat.OpenXml.VariantTypes.VTLPWSTR { Text = DateTime.Today.ToShortDateString() });
properties1.Append(customDocumentProperty1);
part.Properties = properties1;
What type of property should the Publish Date be set as, and what is the right syntax for setting this?
Update: I have found that Publish Date is a CoverPageProperty which can be created using the below snippet, but I still haven't found how to set it correctly within the document.
var coverPageProps = new DocumentFormat.OpenXml.Office.CoverPageProps.CoverPageProperties
{
PublishDate = new PublishDate(DateTime.Today.ToShortDateString())
};
Adding the below code to my SetDocumentProperties method seems to work. I must admit I don't fully understand the below code, so any explanation would still be welcome. Additionally, if anyone has a solution which doesn't include writing XML as a string inside C# I would much prefer to avoid that.
const string publishDatePartId = "publishDatePart";
var publishDateXmlPart = wordDocument.MainDocumentPart.AddNewPart<CustomXmlPart>("application/xml", publishDatePartId);
var writer = new XmlTextWriter(publishDateXmlPart.GetStream(FileMode.Create), System.Text.Encoding.UTF8);
writer.WriteRaw($"<CoverPageProperties xmlns=\"http://schemas.microsoft.com/office/2006/coverPageProps\">" +
$"<PublishDate>{DateTime.Today.ToShortDateString()}</PublishDate>" +
$"</CoverPageProperties>");
writer.Flush();
writer.Close();
var customXmlPropertiesPart = publishDateXmlPart.AddNewPart<CustomXmlPropertiesPart>(publishDatePartId);
customXmlPropertiesPart.DataStoreItem = new DocumentFormat.OpenXml.CustomXmlDataProperties.DataStoreItem()
{
//I don't know what this ID is, but it seems to somehow relate to the Publish Date
ItemId = "{55AF091B-3C7A-41E3-B477-F2FDAA23CFDA}"
};
I have created code that gathers a list of the existing "Line Styles" in Revit.
List<Category> All_Categories = doc.Settings.Categories.Cast<Category>().ToList();
Category Line_Category = All_Categories[1];
foreach (Category one_cat in All_Categories) { if (one_cat.Name == "Lines") { Line_Category = one_cat;} }
if (Line_Category.CanAddSubcategory)
{
CategoryNameMap All_Styles = Line_Category.SubCategories; List<string> Line_Styles = new List<string>();
foreach (Category one_category in All_Styles) { if (one_category.Name.Contains("CO_NAME")) {Line_Styles.Add(one_category.Name); } }
TaskDialog.Show(Line_Styles.Count.ToString() + " Current Line Styles", List_To_Dialog(Line_Styles));
}
This gets the list of line styles, but when I try:
Category New_Line_Style = Line_Category.NewSubCategory....
Visual Studio tells me there is no definition for NewSubCategory
Can anyone tell me how to make a new SubCategory of "Lines", or what I'm doing wrong in the above code?
NOTE: I discovered the main issue. I was attempting to add the sub category to my variable Line_Category (which is itself a category, which should be a parent). I had also attempted adding the sub category to All_Categories (which had been cast as a list and not a CategoryNameMap).
When I added a variable that was not cast, NewSubCategory became available. However, now I am unable to see how to set the line pattern associated with my new style -- the only example I've found online suggests using New_Line_Style.LinePatternId, but that is not in the list of available options on my new SubCategory. Is there some way to set the default pattern to be used when creating a new SubCategory?
Jeremy Tammik wrote a post about retrieving all the linestyles here: http://thebuildingcoder.typepad.com/blog/2013/08/retrieving-all-available-line-styles.html. That might help explain some of the linestyle category stuff in more detail.
Here's another good link asking the same question and how it was solved using VB: http://thebuildingcoder.typepad.com/blog/2013/08/retrieving-all-available-line-styles.html. Here's a C# version of the VB code that worked for a new linestyle:
UIApplication app = commandData.Application;
UIDocument uidoc = app.ActiveUIDocument;
Document ptr2Doc = uidoc.Document;
Category lineCat = ptr2Doc.Settings.Categories.get_Item(BuiltInCategory.OST_Lines);
Category lineSubCat;
string newSubCatName = "NewLineStyle";
Color newSubCatColor = new Color(255, 0, 0); //Red
try
{
using (Transaction docTransaction = new Transaction(ptr2Doc, "hatch22 - Create SubCategory"))
{
docTransaction.Start();
lineSubCat = ptr2Doc.Settings.Categories.NewSubcategory(lineCat, newSubCatName);
lineSubCat.LineColor = newSubCatColor;
docTransaction.Commit();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
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.