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
Related
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;
I have been using Exchange WebServices (EWS) for some time now, in Asp.net C #, to add events in the calendars of Office365 users at my work.
I now needed those same events to appear at Microsoft Teams, with the possibility of going on videoconference.
Events appear but that possibility is not present.
One of the properties of "appointments" is "isOnlineMeeting". I tried to add it, making it true, but it always returns an error saying "Set action is invalid for property.".
In the online searches I have done, I have found that this is a read-only property.
So, is there any chance that I can "force" this property?
I have already configured my Exchange so that, in Outlook online when we do a new event, this is always by videoconference.
Some help?
Thank you!
UPDATE:
By the way, the code that I'm using is:
try {
ExchangeService service = new ExchangeService (ExchangeVersion.Exchange2013_SP1, TimeZoneInfo.FindSystemTimeZoneById ("GMT Standard Time"));
service.Url = new Uri ("https://outlook.office365.com/EWS/Exchange.asmx");
string User = "a#a.net";
string Password = "AAA";
service.Credentials = new NetworkCredential (User, Password);
Appointment appointment = new Appointment (service);
appointment.Subject = "Experiment";
appointment.Location = "Videoconference";
string dataStart = "10-02-2021 19:00:00.000";
string dataEnd = "10-02-2021 20:00:00.000";
appointment.Start = DateTime.Parse (dataStart);
appointment.End = DateTime.Parse (dataEnd);
appointment.Body = "<strong>Ignore! Just a test</strong>";
appointment.IsOnlineMeeting = true;
appointment.RequiredAttendees.Add ("b#a.net");
appointment.Save (SendInvitationsMode.SendOnlyToAll);
} catch (Exception ex) {
}
Posting the Answer for better knowledge
Copying from comments
Could you please try with this document by using Graph API. By using Graph API we can set "isOnlineMeeting" property to true, and "onlineMeetingProvider" property to "teamsForBusiness".
I'm trying to send an EmailMessage using EWS with user A and save the sent item in the SentItems folder of user B. Basically it works. The only problem I encounter, the item is saved as a draft and not as a sent item.
The code:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2, timeZone)
{
Url = new Uri(uri),
Credentials = new WebCredentials(new NetworkCredential(user, password, domain)),
UseDefaultCredentials = false,
};
EmailMessage message = new EmailMessage(service)
{
Subject = subject,
Body = new MessageBody(BodyType.HTML, fullBody)
};
message.From = email;
message.ToRecipients.Add(email);
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
What I tried:
// Simply sends the message
message.Send();
// Sends the item but is it not saved in the sentItems of email-account
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
message.SendAndSaveCopy(folderId);
// Sends the item, saves the item in the right folder, but it is saved as a draft, not as a sent item
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
message.Send();
message.Move(folderId);
What am I missing or doing wrong?
This guy tells to simply save and then save usind the folderId, but in such a scenario I get the following error:
This operation can't be performed because this service object already has an ID. To update this service object, use the Update() method instead.
After some tests I figured out that correct procedure is the following one:
FolderId folderId = new FolderId(WellKnownFolderName.SentItems, email);
message.SendAndSaveCopy(folderId);
However, there must be a bug because the item is always saved in the sentItems folder of the user used to authenticate the ExchangeService ignoring the folderId passed as argument.
The solution (workaround) is to impersonate the user as Microsoft Documentation explains. Here the impersionation code:
service.AutodiscoverUrl(email);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, email);
It works...I wonder if somebody else was able to arrange it in a proper way.
I am get a exception when use my service EWS, this is my code
public class ExchangeHelper
{
ExchangeService exchangeService;
public ExchangeHelper()
{
//Instantiate a new ExchangeService object
exchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
//Set the exchange WebService URL
exchangeService.Url = new Uri("https://hostname/EWS/Exchange.asmx");
//exchangeService.Url = new Uri("https://hostname/EWS/Exchange.asmx");
//Set the credentials of the service to the credentials
//that are associated with the impersonating account.
exchangeService.Credentials = new NetworkCredential(
"user",
"pass",
"Domain.com"
);
}
public void CreateAppointment()
{
var emailAddress = "user#doamin.com";
//Set the ImpersonatedUserId property of the ExchangeService object to identify the impersonated user (target account).
//This example uses the user's SMTP email address.
exchangeService.AutodiscoverUrl(emailAddress);
exchangeService.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
//create a new appointment object
Appointment appointment = new Appointment(exchangeService);
//set appointment properties
appointment.Subject = "test";
appointment.Body = "testBody";
//In MSDN it says that if you dont specify the timezone, it will use the UTC timezone
//but in reality it is not working that way.
//so explicity setting the EST timezone
appointment.StartTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
appointment.Start = DateTime.Now;
appointment.EndTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
appointment.End = DateTime.Now.AddHours(1);
//add required participants
appointment.RequiredAttendees.Add(emailAddress);
newFolder.Save(WellKnownFolderName.Inbox);
appointment.Save(new FolderId(WellKnownFolderName.Drafts, "emailAddress"));
//Set it back to null so that any actions that will be taken using the exchange service
//applies to impersonating account (i.e.account used in network credentials)
exchangeService.ImpersonatedUserId = null;
return the unique identifier that is created
return appointment.Id.UniqueId;
}
The exception is "The account does not have permission to impersonate the requested user"
It's not 100% clear from your post whether the account you use for the credentials on the ExchangeService and for the ImpersonationUserId are the same. If they are, then you would not need to use impersonation and should not set that property. It seems like one should be able to impersonate oneself, but there's no need to do so, hence EWS may throw you back that exception.
If they are different, then it's pretty much as the message says: the account you use for the credentials must have the right to impersonate the user you set. This typically involves some PowerShell magic by the Exchange admin to set this up.
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.