Exchange EWS, Calendar FindItems vs FindAppointments? - c#

I followed this guide to retrieve Meetings in Exchange made via Outlook; https://msdn.microsoft.com/en-us/library/office/dn495614(v=exchg.150).aspx
Everything runs fine, no exceptions but it doesn't return any results. I then tried FindItems instead of FindAppointments and this does return my results. Why does FindAppointments not return the meetings?
I'm creating test appointments in Outlook online. By clicking Menu > Calendar > New, I complete the details of the event and then add attendees before saving. These are returned by FindItems() but there doesn't seem to be a property to retrieve Location and Attendee list? Where FindAppointments would give me the properties I need if the data was returned. I have had Outlook installed on a computer previously where creating a meeting specifically mentions the word 'Meeting' where this appears to be calendar items. I'm not sure what the difference is?
My end goal is when users schedule meetings via Outlook and I'll have an application that retrieves details of those meetings inc. an attendee list and location.
Many thanks for any pointers!

We need to add list of required items to property set, in the given example the property set is restricted.
In property code
// Limit the properties returned to the appointment's subject, start time, and end time.
cView.PropertySet = new PropertySet(AppointmentSchema.Subject, AppointmentSchema.Start, AppointmentSchema.End);
instead of above use below property set,
cView.PropertySet = new PropertySet(AppointmentSchema.Subject, AppointmentSchema.Start, AppointmentSchema.End, AppointmentSchema.Location, AppointmentSchema.RequiredAttendees);
or the best for initial learning is
// Limit the properties returned to the appointment's subject, start time, and end time.
cView.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);

Manged to find a solution taken from this thread
The code could do with a tidy up but it correctly pulls down the appointments and allows me to get the data that I need.
FindItemsResults<Item> result = service.FindItems(WellKnownFolderName.Calendar, new CalendarView(DateTime.Now, DateTime.Now.AddDays(7)));
foreach(Item item in result.Items)
{
ServiceResponseCollection<GetItemResponse> itemResponseCollection = service.BindToItems(new[] { new ItemId(item.Id.UniqueId) }, new PropertySet(BasePropertySet.FirstClassProperties));
foreach(GetItemResponse itemResponse in itemResponseCollection)
{
Appointment appointment = (Appointment)itemResponse.Item;
Console.WriteLine("Subject: " + appointment.Subject);
Console.WriteLine("Location: " + appointment.Location);
Console.WriteLine("AppointmentType: " + appointment.AppointmentType.ToString());
Console.WriteLine("Body: " + appointment.Body);
Console.WriteLine("End: " + appointment.End.ToString());
Console.WriteLine("UniqueId: " + appointment.Id.UniqueId);
Console.WriteLine("Start: " + appointment.Start.ToString());
Console.WriteLine("When: " + appointment.When);
Console.WriteLine("Required Attendees: ");
foreach (var attendee in appointment.RequiredAttendees)
{
Console.WriteLine(attendee.Name);
}
}

Related

datacollection program not correctly collecting

I created an data collection app for our company which collect data from our remote devices.
The data is collected from a datamailbox which is comparable with an database that works like an 10 day buffer to store the data. this is all correctly working.
The data is collected through post api requests. for example :
var url = BuildUrl("syncdata");
var response = webClient.CallApi(url, new NameValueCollection() { { "createTransaction","" }, { "lastTransactionId", transactionId } });
var data = DynamicJson.Parse(response);
transactionId = data.transactionId;
I've been trying to collect multiple devices at one time but the problem is that it starts running and collect the data from the first device which works. Than our second device will start collecting the data but it only starts from where device one ended so i've been losing 12hours of data each run. For performance we use transactionId's.(each set of data has its own Id)
The workflow should be like this :
When the data is retrieved for the first time, the user specifies only
the createTransaction filter. The DataMailbox returns all the data of
all devices gateways – with historical data – of the account along a
transaction ID. For the next calls to the API, the client specifies
both createTransaction and lastTransactionId filters. The
lastTransactionId is the ID of the transaction that was returned by
the latest request. The system returns all the historical
data that has been received by the DataMailbox since the last
transaction and a new transaction ID. deviceIds is an additional
filter on the returned result. You must be cautious when using the
combination of lastTransactionId, createTransaction and deviceIds.
lastTransactionId is first used to determine what set of data — newer
than this transaction ID and from all the Device gateways — must be
returned from the DataMailbox, then deviceIds filters this set of data
to send data only from the desired device gateways. If a first request
is called with lastTransactionId, createTransaction and deviceIds, the
following request — implying a new lastTransactionId — does not
contain values history from the previous lastTransactionId of the
device gateways that were not in the deviceId from previous request.
I'm really struggling with the data collection and have no clue how to use the TransactionId and the LastTransActionId.This is the code for now
try
{
CheckLogin();
using (var webClient = new MyWebClient())
{
bool moreDataAvailable;
int samplesCount = 0;
string transactionId = Properties.Settings.Default.TransactionId;
string lastTransactionId = Properties.Settings.Default.LastTransactionId;
do
{
var url = BuildUrl("syncdata");
var response = webClient.CallApi(url, new NameValueCollection() { { "createTransaction","" }, { "lastTransactionId", transactionId } });
var data = DynamicJson.Parse(response);
transactionId = data.transactionId;
var talk2MMessage = getTalk2MMessageHeader(webClient);
if (talk2MMessage != null)
{
}
foreach (var ewon in data.ewons)
{
Directory.CreateDirectory(ewon.name);
foreach (var tag in ewon.tags)
{
try
{
Console.WriteLine(Path.Combine(ewon.name, tag.name + ""));
foreach (var sample in tag.history)
{
Console.WriteLine(ewon.name + " " + tag.name + " " + tag.description);
Console.WriteLine(transactionId);
samplesCount++;
}
}
catch (RuntimeBinderException)
{ // Tag has no history. If it's in the transaction, it's most likely because it has alarm history
Console.WriteLine("Tag {0}.{1} has no history.", ewon.name, tag.name);
}
}
}
Console.WriteLine("{0} samples written to disk", samplesCount);
// Flush data received in this transaction
if (Properties.Settings.Default.DeleteData)
{
//Console.WriteLine("Flushing received data from the DataMailbox...");
url = BuildUrl("delete");
webClient.CallApi(url, new NameValueCollection() { { "transactionId", transactionId } });
Console.WriteLine("DataMailbox flushed.");
}
//save the transaction id for next run of this program
Properties.Settings.Default.LastTransactionId = lastTransactionId;
Properties.Settings.Default.Save();
// Did we receive all data?
try
{
moreDataAvailable = data.moreDataAvailable;
}
catch (RuntimeBinderException)
{ // The moreDataAvailable flag is not specified in the server response
moreDataAvailable = false;
}
if (moreDataAvailable)
Console.WriteLine("There's more data available. Let's get the next part...");
}
while (moreDataAvailable);
Here are my credentials for starting the collection like all parameters
static void CheckLogin()
{
if (string.IsNullOrEmpty(Properties.Settings.Default.Talk2MDevId))
{
Properties.Settings.Default.Talk2MDevId = Prompt("Talk2MDevId");
Properties.Settings.Default.APIToken = Prompt("API Token");
string deleteInputString = Prompt("Delete data after synchronization? (yes/no)");
Properties.Settings.Default.DeleteData = deleteInputString.ToLower().StartsWith("y");
Properties.Settings.Default.TransactionId = "";
Properties.Settings.Default.LastTransactionId = "";
Properties.Settings.Default.Save();
}
I think it's something with the transactionId and LastTransaction id but i have no clue.
More information can be found here: https://developer.ewon.biz/system/files_force/rg-0005-00-en-reference-guide-for-dmweb-api.pdf
As I understand your question, you problem is that for the first few transactionIds, you only get data from device 1 and then only data from device 2.
I'm assuming the following in my answer:
You didn't specify somewhere else in code the filter on "ewonid"
When you say you lose 12 hours of data , you are assuming it because "device 2" data are streamed after "device 1" data.
You did try without the /delete call with no change
/syncdata is an endpoint that returns a block of data for an account since a given transactionId (or oldest block if you didn't provide a transactionID). This data is sorted by storage date by the server, which depends on multiple factors:
when was the device last "vpn online"
at which frequency the device is pushing data to datamailbox
when was that device packet digested by datamailbox service
You could technically have 1 year old data pushed by a device that gets connected back to vpn now, and those data would be registered in the most recent blocks.
For those reasons, the order of data block is not the order of device recording timestamp. You always have to look at the field ewons[].tags[].history[].date to known when that measure was made.
foreach (var sample in tag.history)
{
Console.WriteLine(ewon.name + " " + tag.name + " " + tag.description);
Console.WriteLine(sample.value + " at " + sample.date);
Console.WriteLine(transactionId);
samplesCount++;
}
In your case, I would assume both devices are configured to push their data once a day, one pushing it's backlog, let's say, at 6AM and the other at 6PM.

Only get the latest reply text in an Outlook Email using VSTO and C#

I have gone through many links already but none of them seems to be working. My problem is that in an Outlook Add-In written using C# and VSTO, I am looking to capture the text of the latest Reply email to a thread.
The problem is that all the properties on a MailItem object such as Body, HTMLBody etc give the entire text of the email including past replies. I am looking to somehow only get the most recent text. And I need to be able to do this considering multiple languages in an email.
Here is what i have tried
Using Bookmarks on the MailEditor - There seems to be no more _MailOriginal bookmark with exchange and Outlook
Somehow trying to get hold off MIME properties - I don't know enough on which properties to pick and how to use them to parse the recent text.
You cannot do that even in theory: imagine a user typing at the top of the message (e.g. "see below") and then inserting/deleting various pieces in the message body below (I do that all the time). You are lucky if the font color is different.
You can try to compare the original with the new and figure out the diff, but that requires access to the original message. You can look at the PR_IN_REPLY_TO_ID MAPI property (DASL name http://schemas.microsoft.com/mapi/proptag/0x1042001F) and try to find the original message either in the Inbox or the Sent Items folder. Note that in the latter case (Sent Items folder) the property might not be available on the cached message, you'd need to search the online version of the folder (cannot do that in OOM, you'd need Extended MAPI in C++ or Delphi or Redemption in any language).
The Outlook object model doesn't provide anything for that. You need to parse the message body string on your own.
Also, you can iterate over all items in the conversation and detect each of them in the latest/recent item. By removing older items you can get the latest. The following example shows how to get and display mail items in a conversation.
void DemoConversation()
{
object selectedItem = Application.ActiveExplorer().Selection[1];
// For this example, you will work only with
//MailItem. Other item types such as
//MeetingItem and PostItem can participate
//in Conversation.
if (selectedItem is Outlook.MailItem)
{
// Cast selectedItem to MailItem.
Outlook.MailItem mailItem = selectedItem as Outlook.MailItem;
// Determine store of mailItem.
Outlook.Folder folder = mailItem.Parent as Outlook.Folder;
Outlook.Store store = folder.Store;
if (store.IsConversationEnabled == true)
{
// Obtain a Conversation object.
Outlook.Conversation conv = mailItem.GetConversation();
// Check for null Conversation.
if (conv != null)
{
// Obtain Table that contains rows
// for each item in Conversation.
Outlook.Table table = conv.GetTable();
Debug.WriteLine("Conversation Items Count: " + table.GetRowCount().ToString());
Debug.WriteLine("Conversation Items from Table:");
while (!table.EndOfTable)
{
Outlook.Row nextRow = table.GetNextRow();
Debug.WriteLine(nextRow["Subject"]
+ " Modified: "
+ nextRow["LastModificationTime"]);
}
Debug.WriteLine("Conversation Items from Root:");
// Obtain root items and enumerate Conversation.
Outlook.SimpleItems simpleItems = conv.GetRootItems();
foreach (object item in simpleItems)
{
// In this example, enumerate only MailItem type.
// Other types such as PostItem or MeetingItem
// can appear in Conversation.
if (item is Outlook.MailItem)
{
Outlook.MailItem mail = item as Outlook.MailItem;
Outlook.Folder inFolder = mail.Parent as Outlook.Folder;
string msg = mail.Subject
+ " in folder " + inFolder.Name;
Debug.WriteLine(msg);
}
// Call EnumerateConversation
// to access child nodes of root items.
EnumerateConversation(item, conv);
}
}
}
}
}
void EnumerateConversation(object item, Outlook.Conversation conversation)
{
Outlook.SimpleItems items = conversation.GetChildren(item);
if (items.Count > 0)
{
foreach (object myItem in items)
{
// In this example, enumerate only MailItem type.
// Other types such as PostItem or MeetingItem
// can appear in Conversation.
if (myItem is Outlook.MailItem)
{
Outlook.MailItem mailItem = myItem as Outlook.MailItem;
Outlook.Folder inFolder = mailItem.Parent as Outlook.Folder;
string msg = mailItem.Subject
+ " in folder " + inFolder.Name;
Debug.WriteLine(msg);
}
// Continue recursion.
EnumerateConversation(myItem, conversation);
}
}
}
In the sample code example, we get a selected MailItem object and then determine the store of the MailItem object by using the Store property of the Folder object. DemoConversation then checks whether the IsConversationEnabled property is true; if it is true, the code example gets Conversation object by using the GetConversation method. If the Conversation object is not a null reference, the example gets the associated Table object that contains each item in the conversation by using the GetTable method. The example then enumerates each item in the Table and calls EnumerateConversation on each item to access the child nodes of each item. EnumerateConversation takes a Conversation object and gets the child nodes by using the GetChildren(Object) method. EnumerateConversation is called recursively until there are no more child nodes. Each conversation item is then displayed to the user.

EWS Performance in C# WinForms application

I'm using EWS in my winforms application to create a new appointment in my Outlook (+ to get items from my Outlook Calendar).
The issue i'm having is the following:
Everything works perfect but currently it takes 20-25 seconds to retrieve my appointments (= calendar items in Outlook) and 13-20 seconds to create an appointment
The code that does this comes straight from 'Google':
private void btn_Test_Click(object sender, EventArgs e)
{
DateTime d1 = DateTime.Now;
ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
try
{
service = new ExchangeService(ExchangeVersion.Exchange2013);
service.Credentials = new WebCredentials("mail", "pass");
/*service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;*/
service.AutodiscoverUrl("mail", RedirectionUrlValidationCallback);
service.Url = new Uri("https://mail.domain.com/EWS/Exchange.asmx");
}
catch (Exception ml2)
{
MessageBox.Show(ml2.ToString());
}
// We get 10 items in the calendar for the next week
DateTime startDate = DateTime.Now;
DateTime endDate = startDate.AddDays(7);
const int NUM_APPTS = 10;
// Initialize the calendar folder object with only the folder ID.
CalendarFolder calendar = CalendarFolder.Bind(service, WellKnownFolderName.Calendar, new PropertySet());
// Set the start and end time and number of appointments to retrieve.
CalendarView cView = new CalendarView(startDate, endDate, NUM_APPTS);
// Limit the properties returned to the appointment's subject, start time, and end time.
cView.PropertySet = new PropertySet(AppointmentSchema.Subject, AppointmentSchema.Start, AppointmentSchema.End);
// Retrieve a collection of appointments by using the calendar view.
FindItemsResults<Appointment> appointments = calendar.FindAppointments(cView);
Console.WriteLine("\nThe first " + NUM_APPTS + " appointments on your calendar from " + startDate.Date.ToShortDateString() +
" to " + endDate.Date.ToShortDateString() + " are: \n");
foreach (Appointment a in appointments)
{
Console.Write("Subject: " + a.Subject.ToString() + " ");
Console.Write("Start: " + a.Start.ToString() + " ");
Console.Write("End: " + a.End.ToString());
Console.WriteLine();
}
DateTime d2 = DateTime.Now;
MessageBox.Show( "Seconds: " + (d2 - d1).TotalSeconds.ToString());
}
Since I have absolutely 0 experience with EWS (or developing while using API's) I was wondering if there was room for performance or I wanted to know if this is just normal? I haven't found anything EWS = SLOW related so I was worrying a bit.
Could it be that my code is wrong or that i need to configure one thing or another server sided to improve results?
Thanks
The most likely thing to slow down you code is
service.AutodiscoverUrl("mail", RedirectionUrlValidationCallback);
service.Url = new Uri("https://mail.domain.com/EWS/Exchange.asmx");
You do an AutoDiscover and then set the link manually which is make the first AutoDiscover Call redundant. Auto-discover will do multiple searches of Local AD domain, DNS records to try and discover the correct URL to use so I would suggest if you are going to hardcode the URL you remark out the first line.
Also your testing logic only looks at the total time to execute you function which isn't going to be helpfully you should look at the time to complete each operation eg
FindItemsResults<Appointment> appointments = calendar.FindAppointments(cView);
or
CalendarFolder calendar = CalendarFolder.Bind(service, WellKnownFolderName.Calendar, new PropertySet());
or any Save, Send type method call when the actually call to the server is made if you time this that will give you a true indication of the speed of each call.

C# - Get Direct Reports under another manager's Direct Reports list

I've been trying to figure out how to do this but I'm always met with a bump on the road. What I'm trying to do is to get the reporting people under my manager's direct reports list; so for example, "Alex" is a direct report under my manager, however, when you go into his organization you see that he also has direct reports that report directly to him - I am trying to get "those" reports not only from his side but from anyone else in the list that has direct reports as well. What is needed for me to effectively execute that idea? Many thanks!
This is my code to only get Direct Reports under my manager tree:
public void GetManagerDirectReports()
{
Application App = new Application();
AddressEntry currentUser = App.Session.CurrentUser.AddressEntry;
if (currentUser.Type == "EX")
{
ExchangeUser manager = currentUser.GetExchangeUser().GetExchangeUserManager();
if (manager != null)
{
AddressEntries addrEntries = manager.GetDirectReports();
if (addrEntries != null)
{
foreach (AddressEntry addrEntry in addrEntries)
{
ExchangeUser exchUser = addrEntry.GetExchangeUser();
StringBuilder sb = new StringBuilder();
sb.AppendLine("Name: "
+ exchUser.Name);
sb.AppendLine("Title: "
+ exchUser.JobTitle);
sb.AppendLine("Department: "
+ exchUser.Department);
sb.AppendLine("Email: "
+ exchUser.PrimarySmtpAddress);
Debug.WriteLine(sb.ToString());
Console.WriteLine(sb.ToString());
Console.ReadLine();
}
}
}
}
}
I opted to go ahead and use LDAP instead of Microsoft's EWS because I saw it uses _ComObject and I don't believe that will work with what I need it to work for. Essentially, I created a master load class and then a sub-class to handle LDAP syntax which would give me the emails of all managers who have direct reports. Something I found useful while doing my research is this filter string which came in quite handy in my time of need (where "cn" is the manager's name):
searcher = new DirectorySearcher
{
Filter = "(&(objectClass=user)(objectCategory=person)(manager=" + cn + ",OU=Unit,OU=People,DC=my,DC=domain,DC=com))"
};
searcher.PropertiesToLoad.Add("DirectReports");
searcher.PropertiesToLoad.Add("mail");
Hope this can serve of some use to coming questions related to this in the future.

How to get "Verification Tab Data" in TFS WorkItem using C#?

I want to fetch the data in one of the WorkItems. I am getting the Description, Created By and all Information (left side only), But I didn't get the right page data Like - Clarification, Planning, Validation and all.
This is the code I am using to get the WorkItem,
WorkItemCollection qryRslts = workItemStore.Query("Select * From WorkItems Where ID = '0000007' ");
Any documents or link related to this will be helpful, Thanks.
Maybe you could try the code below:
WorkItemCollection qryRslts = workItemStore.Query("Select * From WorkItems Where ID = '0000007' ");
foreach (WorkItem workItem in qryRslts)
{
workItem.Open();
workItem.Fields["filed name"].Value = "***";
workItem.Save();
}
If you only want to get one workitem, it's unnecessary to use a query. You could use workItemStore.GetWorkItem(Id) method to get WorkItem class.

Categories