I can't show any code here yet, because i'm still analyzing if it is possible to realize this.
In our company we have a virtual system to make and manage leave applications. I should now check if it would be possible to enter an approved vacation in the Outlook calendar of the applicant.
I would need a central solution which remotely accesses the calendar and enters the appointments. We currently use the on premise solution of Mircosoft Exchange 2019 and Office 365.
During my research I came across EWS but it seems that Exchange 2019 does not support it anymore. Is there possibly another solution which I could use? Basically I would like to realize a solution with C# but I would also be able to realize a Powershell or Java solution. But most of the time I did not find a real solution.
Most of the time the examples are always local on the machines or using an older Exchange Server like 2013. I haven't found reliable information for 2019 yet. I hope someone here can help me or give me a hint. Or it would also be helpful to say if it is not possible.
Best regards!
I am currently working on a solution. I will post the code when I am successfull!**
Exchange 2019 fully supports EWS.
EWS is still the preferred API to access Exchange, even if Microsoft is not adding any new features to it.
ON the client side, you can use Outlook Object Model and its Namespace.GetSharedDefaultFolder methods. Once you have an instance of the MAPIFolder object, you can use MAPIFolder.Items.Add to create a new appointment.
I did choose a little bit different approach but I was able to make it fully work. I now can create events and delete them if needed.
public void UpdateCalender()
{
ExchangeService Service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
Uri Url = new Uri("https://localmaildomain.sys/EWS/Exchange.asmx");
Service.Url = Url;
Service.Credentials = new NetworkCredential("service_user","service_password");
Folder inboxFolder = Folder.Bind(Service, new FolderId(WellKnownFolderName.Calendar, Temp.UserMail));
foreach (var entry in Temp.Positions)
{
if (!entry.Storno)
{
try
{
Appointment appointment = new Appointment(Service);
appointment.Subject = $"Urlaub/Vacation ({entry.Type})";
appointment.Body = $"{entry.Comment}";
appointment.IsAllDayEvent = true;
appointment.Start = entry.Date.AddSeconds(1);
appointment.End = entry.Date.AddSeconds(1);
appointment.LegacyFreeBusyStatus = LegacyFreeBusyStatus.OOF;
appointment.Save(inboxFolder.Id, SendInvitationsMode.SendToNone);
}
catch (Exception Ex)
{
Console.WriteLine($"Calender item could not be created! Exception: {Ex.ToString()}");
}
}
else
{
CalendarView view = new CalendarView(entry.Date, entry.Date);
FindItemsResults<Appointment> results = Service.FindAppointments(inboxFolder.Id,view);
foreach (var appointment in results)
{
if (appointment.Subject == $"Urlaub/Vacation ({entry.Type})" && appointment.Start == entry.Date)
{
try
{
appointment.Delete(DeleteMode.MoveToDeletedItems);
}
catch(Exception Ex)
{
Console.WriteLine($"Calender item could not be deleted! Exception: {Ex.ToString()}");
}
break;
}
}
}
}
}
Related
I have a question about Microsoft Graph API for C# code. Is it possible to get all messages which I have in Outlook?
I found a good article, but I'm still not getting an answer to my question. The article:
Get all email message using Microsoft Graph API in c#
(UPDATED) I found a good video about how to connect to Microsoft Graph: https://www.youtube.com/watch?v=acnFrkBL1kE&ab_channel=Microsoft365Developer
I have still the same question. I can get 1000 emails for call. Is there any way to get all messages from my Outlook? They should be from all my folders.
My new code for call 1000 messages:
public async Task<(IEnumerable<Message> Messages, string NextLink)> GetUserMessagesPage(
string nextPageLink = null, int top = 1000)
{
IUserMessagesCollectionPage pagedMessages;
if(nextPageLink == null)
{
pagedMessages = await _graphServiceClient.Me.Messages.Request().Select(msg => new
{
msg.Subject,
msg.BodyPreview,
msg.ReceivedDateTime
}).OrderBy("receivedDateTime desc").Top(1000).GetAsync();
}
else
{
var messagesCollectionRequest = new UserMessagesCollectionRequest(nextPageLink, _graphServiceClient, null);
pagedMessages = await messagesCollectionRequest.GetAsync();
}
return (Messages: pagedMessages, NextLink: GetNextLink(pagedMessages));
}
(UPDATED) I have tried also this:
pagedMessages = await _graphServiceClient.Users["email#.com"].Messages.Request().Select(msg => new { msg.Subject}).Top(1000).GetAsync();
messages.AddRange(pagedMessages.CurrentPage);
while (pagedMessages.NextPageRequest != null)
{
pagedMessages = await pagedMessages.NextPageRequest.GetAsync();
messages.AddRange(pagedMessages.CurrentPage);
}
It was mentioned here: https://github.com/microsoftgraph/microsoft-graph-docs/blob/main/api-reference/beta/api/user-list-messages.md
From: Microsoft Graph REST API - Get message Documentation
There are two scenarios where an app can get a message in another
user's mail folder:
If the app has application permissions, or,
If the app has the appropriate delegated permissions from one user, and another user has shared a mail folder with that user, or,
has given delegated access to that user. See details and an
example.
Permissions
One of the following permissions is required to call this API. To
learn more, including how to choose permissions, see
Permissions.
Delegated (work or school account) - Permissions: Mail.ReadBasic, Mail.Read
Delegated (personal Microsoft account) - Permissions: Mail.ReadBasic, Mail.Read
Application - Permissions: Mail.ReadBasic.All, Mail.Read
I am using C# with the Exchange Web Service (EWS).
I have meetings that I need to forward and whenever I do they get stripped, but only from some accounts and not others. The link for the meeting is still there but it is not being recognised by the online Outlook as a meeting item, nor by Teams which is connected to the account.
This even happens if I manually forward, but again only if I forward emails that are from some accounts - some other accounts are fine!
I'm using this on incoming emails:
var fwdEmailArr = new EmailAddress[1];
fwdEmailArr [0] = fwdEmail;
MeetingRequest appointment = MeetingRequest.Bind(service, email.Id);
appointment.Forward("", fwdEmailArr);
This is the same issue if I use the email.forward as well, etc.
However, if I create a new appointment and send it, it doesn't get stripped - this is with the same addresses.
Appointment appt = new Appointment(service);
appt.Subject = email.Subject;
appt.Body = appointment.Body;
appt.Start = appointment.Start;
appt.End = appointment.End;
appt.Location = appointment.Location;
appt.RequiredAttendees.Add(fwdEmail);
foreach (var reqAtt in appt.RequiredAttendees)
{
appt.RequiredAttendees.Add(reqAtt);
}
foreach (var reqAtt in appt.OptionalAttendees)
{
appt.OptionalAttendees.Add(reqAtt);
}
appt.RequiredAttendees.Add(appointment.From.Address);
appt.Save(SendInvitationsMode.SendToAllAndSaveCopy);
So, I could do this but it means that they are no longer the same meeting and declining the original wont decline this. Unless there's a way I can connect the meetings or something?
Any ideas how I can stop the meeting being stripped?
Or alternatively just add another recipient to the current meeting, that will show on their calendar?
If anyone comes here with a similar issue, it turns out that, first of all you need to make sure you define the correct server version on the service declaration:
service = new ExchangeService(ExchangeVersion.Exchange2016){}
In addition, for some reason some images when attached to the forwarded email for some reason confuse EWS and make it think there's no meeting. I got around this by scanning the MIME content and just extracting the calendar block and deleting all other attachments.
This has been working flawlessly for about 5 months.
I'm new to EWS so forgive me if this is obvious. I searched and could not find a solution.
I have a command line C# utility that filters emails and organizes them into folders. The app seems to work fine logically, but appears to be timing out after 15 minutes without error.
The utility connects using autodiscover:
private ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
...
// Connect using credentials and autodiscover
service.Credentials = new WebCredentials(userName, password, domain);
service.AutodiscoverUrl(emailServer);
Then after filtering, it loops through the results and moves the emails into the appropriate folders. Something like this:
...
// Create a filter to only return unread messages
SearchFilter emailFilter = new SearchFilter.IsLessThan(EmailMessageSchema.DateTimeReceived, DateTime.Now.AddDays(-2));
// Retrieve the results
FindItemsResults<Item> findResults = service.FindItems(folder, emailFilter, view);
Logger.Write("Total emails to be archived: " + findResults.TotalCount);
...
try
{
...
foreach (Item email in findResults)
{
EmailMessage emailMessage = EmailMessage.Bind(service, email.Id);
emailMessage.Load();
emailMessage.Move(folder5.Folders[0].Id);
}
}
catch (Exception ex)
{
Logger.Write("Exception caught while moving emails: " + ex.Message);
throw ex;
}
...
Logger.Write("=== End Email Move Logic...");
My first logging output indicates that 5,000 emails should be moved, and in fact, the process begins nicely, but after 15 minutes, I see the final logging output. Indicating that everything completed. Yet, only a portion of the total 5000 get moved. Oddly, I do not see an exception or anything in the event logs.
EDIT: 7/9/2014
Ok, my initial problem was due to the fact that I did not notice that the FindItemsResults class uses paging! Doh! So even though it correctly reported the TotalCount (5000), when I iterated through the results, only 1000 were being processed. Not terribly intuitive to a EWS noob like me, but there you have it.
Unfortunately, I have a follow-up frustration. Based on Brad's comments below, I'm now using the ExchangeService.MoveItems method. After dealing with the paging issue, I now have a proper list of itemIds (count = 5000) that I pass to the MoveItems method. Like this:
service.MoveItems(itemIds, folder5.Folders[0].Id);
To my surprise, only 730 emails were moved. Leaving 4270 emails unmoved. So, hoping to find a pattern, I ran it again, this time 671 emails were moved. No pattern.
Any input would be greatly appreciated.
I appreciate this is an old thread but it was the first I found on Google so thought I would add my findings.
I coded a WPF app to help in migrating legacy Exchange public folders to Office 365 hosted Exchange and have been experiencing timeouts when searching using the FindItems method. Specifically I was searching over a date range for date sent in a public folder and then copying the items found to the Office 365 public folder. If we used a large date range then the FindItems call would timeout.
What has fixed it for us is to set the Timeout value for the ExchangeService, the default is 100000 milliseconds, so we just added another 0 to test the possible solution. Hey presto we were able to use much larger date ranges.
ExchangeService service = new ExchangeService();
ExchangeService o365 = new ExchangeService();
switch (strArgs[6])
{
case "0":
service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
break;
case "1":
service = new ExchangeService(ExchangeVersion.Exchange2010);
break;
case "2":
service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
break;
case "3":
service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
break;
case "4":
service = new ExchangeService(ExchangeVersion.Exchange2013);
break;
case "5":
service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
break;
}
int nCount = 0;
// Connect to on-premises Exchange Server
callback(nCount, "Connecting to on-premises Exchange Server, please wait....", 0, true, mailItem);
service.Credentials = new WebCredentials(strArgs[0], strArgs[1]);
if (!useUrl)
service.AutodiscoverUrl(strArgs[2], RedirectionUrlValidationCallback);
else
service.Url = new Uri(strArgs[10] + "/EWS/Exchange.asmx");
service.Timeout = 1000000;
callback(nCount, "Connected to on-premises Exchange Server, please wait....", 0, true, mailItem);
// Connect to Office 365 Exchange Server
callback(nCount, "Connecting to Office 365 Exchange Server, please wait....", 0, true, mailItem);
o365.Credentials = new WebCredentials(strArgs[7], strArgs[8]);
o365.AutodiscoverUrl(strArgs[9], RedirectionUrlValidationCallback);
callback(nCount, "Connected to Office 365 Exchange Server, please wait....", 0, true, mailItem);
We are using a paging value of 500.
The source Exchange version is Exchange 2007 SP1.
Hope it helps someone.
Timeouts are normal for search queries, especially for large folders.
What happens is Exchange does not keep live indexes to respond to just any search query you can build, so it times out the response and keeps indexing.
If you try to issue the same query later it will succeed. Unfortunately Exchange does not provide information on when, just keep trying. After first page is returned further page queries will return almost immediately.
BTW this is not a throttling issue, you will get a clear error back when throttling limit is reached. Exchange 2007 just says that, 2010 and higher provides you a value of how much to wait until throttling quota will be recharged.
It's about Outlook 2003, Exchange 2010 (SP2) and the EWS managed API in C#.
I'm trying to get all appointments from the Exchangeserver, this works fine:
ExchangeService service = exchangeService.Service;
CalendarFolder calendarfolder = CalendarFolder.Bind(service, WellKnownFolderName.Calendar);
ItemView view = new ItemView(calendarfolder.TotalCount);
if (calendarfolder.TotalCount > 0)
{
FindItemsResults<Item> calendarItems = service.FindItems(WellKnownFolderName.Calendar, view);
foreach (Item item in calendarItems)
{
if (item is Appointment)
{
//...do something
}
}
}
The first line returns an ExchangeService with all required preferences.
The only problem is this one: I can't get the associated contacts of the appointment. I really need them - not the invited users, only the associated contacts.
Every other attribute is available, except this one...
Please, anyone an idea? I'm working on this since months, and I suspect I already know almost every single article about the EWS managed API on Google without any success on this point.
Thanks in advance.
I need the ability to monitor for and read e-mail from a particular mailbox on a MS Exchange Server (internal to my company). I also need to be able to read the sender's e-mail address, subject, message body and download an attachment, if any.
What is the best way to do this using C# (or VB.NET)?
It's a mess. MAPI or CDO via a .NET interop DLL is officially unsupported by Microsoft--it will appear to work fine, but there are problems with memory leaks due to their differing memory models. You could use CDOEX, but that only works on the Exchange server itself, not remotely; useless. You could interop with Outlook, but now you've just made a dependency on Outlook; overkill. Finally, you could use Exchange 2003's WebDAV support, but WebDAV is complicated, .NET has poor built-in support for it, and (to add insult to injury) Exchange 2007 nearly completely drops WebDAV support.
What's a guy to do? I ended up using AfterLogic's IMAP component to communicate with my Exchange 2003 server via IMAP, and this ended up working very well. (I normally seek out free or open-source libraries, but I found all of the .NET ones wanting--especially when it comes to some of the quirks of 2003's IMAP implementation--and this one was cheap enough and worked on the first try. I know there are others out there.)
If your organization is on Exchange 2007, however, you're in luck. Exchange 2007 comes with a SOAP-based Web service interface that finally provides a unified, language-independent way of interacting with the Exchange server. If you can make 2007+ a requirement, this is definitely the way to go. (Sadly for me, my company has a "but 2003 isn't broken" policy.)
If you need to bridge both Exchange 2003 and 2007, IMAP or POP3 is definitely the way to go.
Um,
I might be a bit too late here but isn't this kinda the point to EWS ?
https://msdn.microsoft.com/en-us/library/dd633710(EXCHG.80).aspx
Takes about 6 lines of code to get the mail from a mailbox:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
//service.Credentials = new NetworkCredential( "{Active Directory ID}", "{Password}", "{Domain Name}" );
service.AutodiscoverUrl( "First.Last#MyCompany.com" );
FindItemsResults<Item> findResults = service.FindItems(
WellKnownFolderName.Inbox,
new ItemView( 10 )
);
foreach ( Item item in findResults.Items )
{
Console.WriteLine( item.Subject );
}
Graph - currently, the preferred unified API (pure HTTP based) to access emails, contacts, appointments, tasks etc., residing on Exchange Server along with other data hosted by Microsoft (Teams, Sharepoint, etc.).
Use Graph Explorer or OutlookSpy (I am its author) to play with the API.
Microsoft provides Graph SDK for use in several languages
EWS. Fully supported. It is purely HTTP based and can be accessed from any language, but there are .Net and Java specific libraries.
MS has indicated that no new features will be added, and that Graph is preferable, even though not all EWS features are yet available in Graph (such as high fidelity Fast Transfer Stream export/import exposed through ExportItems/ImportItems).
You can use EWSEditor or OutlookSpy (I am its author) to play with the API.
Extended MAPI. This is the native API used by Outlook. It ends up using the MSEMS Exchange MAPI provider, which can talk to Exchange using RPC (Exchange 2013 no longer supports it) or RPC-over-HTTP (Exchange 2007 or newer) or MAPI-over-HTTP (Exchange 2013 and newer).
The API itself can only be accessed from unmanaged C++ or Delphi. You can also use Redemption (any language, I am its author) - its RDO family of objects is an Extended MAPI wrapper. To use Extended MAPI, you need to install either Outlook or the standalone (Exchange) version of MAPI (on extended support, and it does not support Unicode PST and MSG files and cannot access Exchange 2016). Extended MAPI can be used in a service.
You can play with the API using OutlookSpy (I am its author) or MFCMAPI.
Outlook Object Model - not Exchange specific, but it allows access to all data available in Outlook on the machine where the code runs. Cannot be used in a service.
Exchange Active Sync. Microsoft no longer invests any significant resources into this protocol.
Outlook used to install CDO 1.21 library (it wraps Extended MAPI), but it had been deprecated by Microsoft and no longer receives any updates.
There used to be a third-party .Net MAPI wrapper called MAPI33, but it is no longer being developed or supported.
WebDAV - deprecated.
Collaborative Data Objects for Exchange (CDOEX) - deprecated.
Exchange OLE DB Provider (EXOLEDB) - deprecated.
Here is some old code I had laying around to do WebDAV. I think it was written against Exchange 2003, but I don't remember any more. Feel free to borrow it if its helpful...
class MailUtil
{
private CredentialCache creds = new CredentialCache();
public MailUtil()
{
// set up webdav connection to exchange
this.creds = new CredentialCache();
this.creds.Add(new Uri("http://mail.domain.com/Exchange/me#domain.com/Inbox/"), "Basic", new NetworkCredential("myUserName", "myPassword", "WINDOWSDOMAIN"));
}
/// <summary>
/// Gets all unread emails in a user's Inbox
/// </summary>
/// <returns>A list of unread mail messages</returns>
public List<model.Mail> GetUnreadMail()
{
List<model.Mail> unreadMail = new List<model.Mail>();
string reqStr =
#"<?xml version=""1.0""?>
<g:searchrequest xmlns:g=""DAV:"">
<g:sql>
SELECT
""urn:schemas:mailheader:from"", ""urn:schemas:httpmail:textdescription""
FROM
""http://mail.domain.com/Exchange/me#domain.com/Inbox/""
WHERE
""urn:schemas:httpmail:read"" = FALSE
AND ""urn:schemas:httpmail:subject"" = 'tbintg'
AND ""DAV:contentclass"" = 'urn:content-classes:message'
</g:sql>
</g:searchrequest>";
byte[] reqBytes = Encoding.UTF8.GetBytes(reqStr);
// set up web request
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://mail.domain.com/Exchange/me#domain.com/Inbox/");
request.Credentials = this.creds;
request.Method = "SEARCH";
request.ContentLength = reqBytes.Length;
request.ContentType = "text/xml";
request.Timeout = 300000;
using (Stream requestStream = request.GetRequestStream())
{
try
{
requestStream.Write(reqBytes, 0, reqBytes.Length);
}
catch
{
}
finally
{
requestStream.Close();
}
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
try
{
XmlDocument document = new XmlDocument();
document.Load(responseStream);
// set up namespaces
XmlNamespaceManager nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("a", "DAV:");
nsmgr.AddNamespace("b", "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/");
nsmgr.AddNamespace("c", "xml:");
nsmgr.AddNamespace("d", "urn:schemas:mailheader:");
nsmgr.AddNamespace("e", "urn:schemas:httpmail:");
// Load each response (each mail item) into an object
XmlNodeList responseNodes = document.GetElementsByTagName("a:response");
foreach (XmlNode responseNode in responseNodes)
{
// get the <propstat> node that contains valid HTTP responses
XmlNode uriNode = responseNode.SelectSingleNode("child::a:href", nsmgr);
XmlNode propstatNode = responseNode.SelectSingleNode("descendant::a:propstat[a:status='HTTP/1.1 200 OK']", nsmgr);
if (propstatNode != null)
{
// read properties of this response, and load into a data object
XmlNode fromNode = propstatNode.SelectSingleNode("descendant::d:from", nsmgr);
XmlNode descNode = propstatNode.SelectSingleNode("descendant::e:textdescription", nsmgr);
// make new data object
model.Mail mail = new model.Mail();
if (uriNode != null)
mail.Uri = uriNode.InnerText;
if (fromNode != null)
mail.From = fromNode.InnerText;
if (descNode != null)
mail.Body = descNode.InnerText;
unreadMail.Add(mail);
}
}
}
catch (Exception e)
{
string msg = e.Message;
}
finally
{
responseStream.Close();
}
}
return unreadMail;
}
}
And model.Mail:
class Mail
{
private string uri;
private string from;
private string body;
public string Uri
{
get { return this.uri; }
set { this.uri = value; }
}
public string From
{
get { return this.from; }
set { this.from = value; }
}
public string Body
{
get { return this.body; }
set { this.body = value; }
}
}
I used code that was published on CodeProject.com. If you want to use POP3, it is one of the better solutions that I have found.
If your Exchange server is configured to support POP or IMAP, that's an easy way out.
Another option is WebDAV access. there is a library available for that. This might be your best option.
I think there are options using COM objects to access Exchange, but I'm not sure how easy it is.
It all depends on what exactly your administrator is willing to give you access to I guess.
You should be able to use MAPI to access the mailbox and get the information you need. Unfortunately the only .NET MAPI library (MAPI33) I know of seems to be unmaintained. This used to be a great way to access MAPI through .NET, but I can't speak to its effectiveness now. There's more information about where you can get it here: Download location for MAPI33.dll?
I got a solution working in the end using Redemption, have a look at these questions...
Using Redemption...
Using Redemption on a 64 bit machine