Delete a calendar appointment from all users with EWS Managed API - c#

I have an application that creates appointments in calendars in Exchange Online for Office 365. I'm using EWS Managed API.
public void CreateAppoitment(string principalName, int taskId) {
ExchangeService service = createService(principalName);
ItemView itemView = new ItemView(1000);
itemView.PropertySet = new PropertySet(BasePropertySet.IdOnly);
List<Appointment> toCreate = new List<Appointment>();
// Create the appointment.
Appointment appointment = new Appointment(service);
// Set properties on the appointment.
appointment.Subject = "Test Appointment";
appointment.Body = "The appointment ...";
appointment.Start = new DateTime(2014, 6, 18, 9, 0, 0);
appointment.End = appointment.Start.AddDays(2);
ExtendedPropertyDefinition epdTaskId = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Appointment, "TASK_Id", MapiPropertyType.Integer);
appointment.SetExtendedProperty(epdTaskId, taskId);
appointment.IsResponseRequested = false;
toCreate.Add(appointment);
ServiceResponseCollection<ServiceResponse> createResponse = service.CreateItems(toCreate, WellKnownFolderName.Calendar, MessageDisposition.SaveOnly, SendInvitationsMode.SendToNone);
}
Note I'm setting ExtendedPropertyDefinition "TASK_Id"
I'm using impersonate to create appointments in users's calendars:
private ExchangeService createService(string principalName) {
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
service.UseDefaultCredentials = false;
service.Credentials = new WebCredentials("XXXX", "YYYY");
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.PrincipalName, principalName);
return service;
}
Then, given a taskId, I want to delete all appointments with this taskId:
public void DeleteAppointment(string principalName, int appointmentId) {
ExchangeService service = createService(principalName);
ItemView itemView = new ItemView(1000);
itemView.PropertySet = new PropertySet(BasePropertySet.IdOnly);
ExtendedPropertyDefinition epdTaskId = new ExtendedPropertyDefinition(
DefaultExtendedPropertySet.Appointment, "TASK_Id", MapiPropertyType.Integer);
SearchFilter filterOnTaskId = new SearchFilter.IsEqualTo(epdTaskId, appointmentId);
FindItemsResults<Item> appointments = service.FindItems(WellKnownFolderName.Calendar, filterOnTaskId, itemView);
List<ItemId> toDelete = appointments.Select(item => item.Id).ToList();
if (toDelete.Count > 0) {
ServiceResponseCollection<ServiceResponse> response = service.DeleteItems(
toDelete, DeleteMode.MoveToDeletedItems, SendCancellationsMode.SendToNone,
AffectedTaskOccurrence.SpecifiedOccurrenceOnly);
foreach (ServiceResponse del in response) {
if (del.Result == ServiceResult.Error) {
//...
}
}
}
}
But this way service.FindItems() only returns the principalName's appointment with TASK_Id = taskId and I want appointments of all users. Is there a way to to this?

The Exchange Managed API and Exchange Web Services only give access to the calendar of one user at a time -- either directly by using the credentials of a user or indirectly by using impersonation to give a service account access to a user's calendar.
To search multiple calendars at once requires a different technique. One option that comes to mind is using the eDiscovery operations in Exchange 2013. Although they are usually used to find email for legal reasons, you may be able to use the same process. Unfortunately, the documentation for eDiscovery is pretty sparse right now, but you can see the EWS operations that are available here: eDiscovery in EWS in Exchange, and you can find the corresponding EWS Managed API methods on the ExchangeService object.

Related

Read email from outlook - problem with XML

I'm trying to connect to outlook online and download some emails with predefined conditions.
in browser when I go to https://outlook.office.com/mail/ and logon with specific email and pass, I can see all mails.
I have c# console application that should connect, search and manipulate with emails.
public void EmailReceiver()
{
try
{
ExchangeService exchangeService = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
exchangeService.Credentials = new NetworkCredential("username", "pass");
exchangeService.Url = new Uri("https://outlook.office.com/mail/");
var inbox = Folder.Bind(exchangeService, WellKnownFolderName.Inbox);
var sf1 = new SearchFilter.ContainsSubstring(EmailMessageSchema.From, "searchMail");
SearchFilter.SearchFilterCollection searchFilterCollection = new SearchFilter.SearchFilterCollection(LogicalOperator.Or);
searchFilterCollection.Add(sf1);
var view = new ItemView(1000);
var findResults = service.FindItems(WellKnownFolderName.Inbox, searchFilterCollection, view);
return;
//foreach (Item item in findResults)
//{
// GetAttachmentsFromEmail(item.Id);
//}
}
catch (Exception ex)
{
throw;
}
}
But cannot connect to outlook
Any suggestions?
Office 365 no longer allows basic authentication, you need to switch to OAuth2. You can still allow basic auth, but you must explicit do so in the Exchange admin console for your tenant. But even in that case, you need to use WebCredentials class, not NetworkCredential. In case of OAuth, you need OAuthCredentials class.
You might also want to set TraceEnabled property to true and provide your class (assign it to the TraceListener property) to log all EWS calls.
exchangeService.TraceEnabled = true;
exchangeService.TraceListener = new MyTraceListener(this);
exchangeService.TraceFlags = TraceFlags.All;

Fetch emails received on certain day from non-Well-known folder in shared inbox, Exchange 2016 EWS in C#

I'm trying to fetch all messages from a user-specified date on an Exchange 2016 server using the EWS managed API in C#.
I authenticate with:
public static void Login(string username, string password)
{
service.UseDefaultCredentials = false;
service.Credentials = new WebCredentials(username, password);
service.AutodiscoverUrl(username, RedirectionUrlValidationCallback);
}
Then select the appropriate inbox with
sharedMailbox = new Mailbox(Properties.Settings.Default.Inbox);
the SMTP address is stored in Settings.settings .
I then find the desired folder using the following (from this thread):
targetFolderId = new FolderId(WellKnownFolderName.Inbox, sharedMailbox);
// set folder view
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(WellKnownFolderName.Inbox, view);
foreach(Folder f in folderResults)
{
if(f.DisplayName == "Invoices")
{
targetFolderId = f.Id;
//tried showing a message box here
}
}
And use the following (filter code from here and retrieve details from Exchange server code from here) to get the messages I want:
public static void FetchUnreadMessages(DateTime searchDate)
{
SearchFilter greaterthanfilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, searchDate);
SearchFilter lessthanfilter = new SearchFilter.IsLessThan(ItemSchema.DateTimeReceived, searchDate.AddDays(1));
SearchFilter dayFilter = new SearchFilter.SearchFilterCollection(LogicalOperator.And, greaterthanfilter, lessthanfilter);
results = service.FindItems(targetFolderId, dayFilter, view);
foreach(var item in results.Items)
{
emails.Add((EmailMessage)item);
}
PropertySet properties = (BasePropertySet.FirstClassProperties);
service.LoadPropertiesForItems(emails, properties);
}
I'm not sure where this is breaking down. I tried showing a message box in the foreach loop that finds the folder with the specified name, and it appears the folder is never found. I know there is a folder with that display name in the shared inbox.
I'm not great at debugging and unfortunately my grasp of the EWS API is pretty shaky. Any suggestions as to what I'm missing are welcome.
I keep everything related to the inbox in a static class so I only have to worry about one instance.
There is some problems with you code first
Then select the appropriate inbox with
sharedMailbox = new Mailbox(Properties.Settings.Default.Inbox);
targetFolderId = new FolderId(WellKnownFolderName.Inbox, sharedMailbox);
Nothing wrong with this code but you probably need to understand what's happening here. This code doesn't make any calls to the server it just setups a FolderId class that you can use in a call to get one of the well known folder.
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(WellKnownFolderName.Inbox, view);
This code would just search the Inbox Subfolder of the Mailbox who's credentials you are using. eg
service.Credentials = new WebCredentials(username, password);
If you wanted to Search the SubFolders of the Inbox of the SharedMailbox you would use
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(targetFolderId , view);
because you are using the targetFolderId in the FindFolder operations that is telling Exchange to Search the Shared Mailbox rather then the mailbox associated with the credentials you are using.

EWS Oauth Exception: The request failed. The remote server returned an error: (401) Unauthorized

I'm trying to create a bot that can book meetings. In order to do that i need to access the calendar of an employee to get FreeBusy info to ultimately book a meeting. I'm trying to avoid hardcoding the email and password and for that I want to use an access token from Azure AD to call EWS.
I set the properties for
public static ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
using this method:
public static async System.Threading.Tasks.Task UseExchangeService(IDialogContext context, string userEmailAddress, SecureString userPassword)
{
string authority = ConfigurationManager.AppSettings["authority"];
string clientID = ConfigurationManager.AppSettings["clientID"];
string resource = ConfigurationManager.AppSettings["resource"];
string appKey = ConfigurationManager.AppSettings["appkey"];
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
ClientCredential clientCred = new ClientCredential(clientID, appKey);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resource, clientCred);
service.Url = new Uri(ConfigurationManager.AppSettings["serverName"] + "/ews/exchange.asmx");
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
service.Credentials = new OAuthCredentials(authenticationResult.AccessToken);
// USING THIS LINE IT WORKS FINE!
// service.Credentials = new NetworkCredential(userEmailAddress, userPassword); // VIRKER
}
I do get the access token from Azure AD and I have granted the permission for the application in Azure AD.
I use this method to extract the freebusytimes, it contains other more code to display the times as buttons on a herocard, but this is the call to EWS:
List<AttendeeInfo> attendees = new List<AttendeeInfo>();
attendees.Add(new AttendeeInfo()
{
SmtpAddress = "MyEMAIL",
AttendeeType = MeetingAttendeeType.Organizer
});
attendees.Add(new AttendeeInfo()
{
SmtpAddress = BookersEmail,
AttendeeType = MeetingAttendeeType.Required
});
//DateTime date1 = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day.Ad, 7, 0, 0)
// Specify options to request free/busy information and suggested meeting times.
AvailabilityOptions availabilityOptions = new AvailabilityOptions();
availabilityOptions.GoodSuggestionThreshold = 49;
availabilityOptions.MaximumNonWorkHoursSuggestionsPerDay = 0;
availabilityOptions.MaximumSuggestionsPerDay = 20;
// Note that 60 minutes is the default value for MeetingDuration, but setting it explicitly for demonstration purposes.
availabilityOptions.MeetingDuration = 60;
availabilityOptions.MinimumSuggestionQuality = SuggestionQuality.Excellent;
//TimeWindow hej = new TimeWindow();
DateTime StartDay = DateTime.Now.AddDays(1);
TimeSpan ts = new TimeSpan(9, 0, 0);
DateTime StartTime = StartDay.Date + ts;
availabilityOptions.DetailedSuggestionsWindow = new TimeWindow(StartTime, DateTime.Now.AddDays(4));
availabilityOptions.RequestedFreeBusyView = FreeBusyViewType.FreeBusy;
// Return free/busy information and a set of suggested meeting times.
// This method results in a GetUserAvailabilityRequest call to EWS.
GetUserAvailabilityResults results = service.GetUserAvailability(attendees,
availabilityOptions.DetailedSuggestionsWindow,
AvailabilityData.FreeBusyAndSuggestions,
availabilityOptions);
I have created the application in Azure AD and I have granted the following permissions:
Office 365 Exchange Online:
Use Exchange Web Services with full access to all mailboxes
Read and write calendars in all mailboxes
Read and write user and shared calendars
Access mailboxes as the signed-in user via Exchange Web Services
I've tried other answers i found on stackoverflow, however they do not do the trick for me.
Hope You can help
I am not familiar with EWS, however as far as I know that the Microsoft Graph also provide the similar feature for find the available meeting time using the rest below( refer here):
POST /me/findMeetingTimes
And if you want to using this REST for the web application so that your web app can delegate the sign-in user to perform the operation for Exchange, we can use the OAuth 2.0 code grant flow. And for how to use this flow to integrate with Microsoft Graph, you can refer the links below:
Get started with Microsoft Graph in an ASP.NET 4.6 MVC app
And here is the detail for this flow:
Authorize access to web applications using OAuth 2.0 and Azure Active Directory
Hope it is helpful.

Exchange Server doesn't support the requested version

I get this error because the FindItemsResult isn't compatible with exchange version I'm using which is 2013.
Exchange Server doesn't support the requested version.
My codes:
SearchFilter sf = new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false));
FindItemsResults<Item> items = service.FindItems(WellKnownFolderName.Inbox, sf, new ItemView(10));
foreach (Item item in items.Items)
{
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.TextBody);
EmailMessage email = EmailMessage.Bind(service, item.Id, propSet);
Program.SearchItems(email);
}
I could just change it into Exchange 2010 but I get an error in TextBody since this is only for Exchange 2013 and later versions.
Is there any way to convert the code which can work in Exchange 2013?
You need to show more of the code your using as your question doesn't really make sense. The ItemSchema.TextBody was added in Exchange 2013 so as long you are running Exchange 2013 and you have set the Initial server version correctly it will work (So you are either not running 2013 or you have other issue in the code you haven't show). If your looking for something that will work on both Exchange 2007,2010 and 2013 the I would suggest you use.
String MailboxToAccess = "user#domain.com";
ExchangeService service = new Microsoft.Exchange.WebServices.Data.ExchangeService(ExchangeVersion.Exchange2010_SP1);
SearchFilter sfSearchFilter = new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead,false);
service.Credentials = new NetworkCredential("user#domain.com", "password");
service.AutodiscoverUrl(MailboxToAccess, adAutoDiscoCallBack);
FolderId FolderToAccess = new FolderId(WellKnownFolderName.Inbox, MailboxToAccess);
ItemView ivItemView = new ItemView(10);
FindItemsResults<Item> FindItemResults = service.FindItems(FolderToAccess, sfSearchFilter, ivItemView);
PropertySet ItemPropertySet = new PropertySet(BasePropertySet.IdOnly);
ItemPropertySet.Add(ItemSchema.Body);
ItemPropertySet.RequestedBodyType = BodyType.Text;
if (FindItemResults.Items.Count > 0)
{
service.LoadPropertiesForItems(FindItemResults.Items, ItemPropertySet);
}
foreach (Item item in FindItemResults.Items)
{
Console.WriteLine(item.Body.Text);
}
internal static bool adAutoDiscoCallBack(string redirectionUrl)
{
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https")
{
result = true;
}
return result;
}
That will return just the Text body and will work on any version of EWS.
Cheers
Glen

Allow impersonation using ews

I'm trying to create a program in C# which should make it able to create appointments in someone else's Outlook calendar. I have code which makes it able to create appointments in my own calendar. I searched on Google and I found I should use impersonation. So I added the line:
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
This is my code:
private void button2_Click(object sender, EventArgs e) {
try{
ExchangeService service = new ExchangeService();
service.UseDefaultCredentials = true;
service.Credentials = new WebCredentials("Test#domain.com", "password");
service.AutodiscoverUrl("Test#domain.com", adAutoDiscoCallBack);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "Test2#domain.com");
Appointment appointment = new Appointment(service);
// Set the properties on the appointment object to create the appointment.
appointment.Subject = "Tennis lesson";
appointment.Body = "Focus on backhand this week.";
appointment.Start = DateTime.Now.AddDays(2);
appointment.End = appointment.Start.AddHours(1);
appointment.Location = "Tennis club";
appointment.ReminderDueBy = DateTime.Now;
// Save the appointment to your calendar.
appointment.Save(SendInvitationsMode.SendToNone);
// Verify that the appointment was created by using the appointment's item ID.
Item item = Item.Bind(service, appointment.Id, new PropertySet(ItemSchema.Subject));
}
}catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
internal static bool adAutoDiscoCallBack(string redirectionUrl) {
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https") {
result = true;
}
return result;
}
The problem is that I keep getting this error ""The SMTP-address has no mailbox associated with it."
Is it because impersonation isn't allowed on the server? If so how do I allow it?
I hope someone can help.
Ps: Sorry for the bad english
A few suggestions if this is Office365 or Exchange 2013 you first need the PrimarySMTP address of the Mailbox you wish to access eg
String MailboxToAccess = "PrimarySMTP#domain.demo";
Note for some tenants the SMTP and UPN/Logon are the same but that is not always the case.
This is what user you are going to be impersonating eg
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, MailboxToAccess);
You should also add in the X-AnchorMailbox header https://learn.microsoft.com/en-us/archive/blogs/webdav_101/best-practices-ews-authentication-and-access-issues eg
service.HttpHeaders.Add("X-AnchorMailbox", MailboxToAccess);
Also when you go to save the Appointment use the FolderId class with the Mailbox overload to ensure your hitting the correct Mailbox eg
FolderId CalendarFolderId = new FolderId(WellKnownFolderName.Calendar, MailboxToAccess);
appointment.Save(CalendarFolderId,SendInvitationsMode.SendToNone);
Cheers
Glen

Categories

Resources