The Problem
I have a Visual Studios 2015 Console Application with the Microsoft.Exchange.WebServices v2.2.0 NuGet package installed. I'm trying to create an appointment, update it, and cancel it while retaining the correct Timezone in the "When" string generated automatically in the calendar invite body. Currently, the initial creation has the correct timezone, but any subsequent updates cause the timezone to revert to UTC.
Note: We have an Exchange 2010 server and use Outlook 2013 clients.
Code Sample
using System;
using System.Globalization;
using Microsoft.Exchange.WebServices.Data;
namespace EWSTesting
{
class Program
{
private const string EmailServer = ""; //replace with your Exchange server
private const string EmailAddress = ""; //replace with your email
static void Main(string[] args)
{
Console.WriteLine("Current Timezone: " + TimeZoneInfo.Local.DisplayName);
var exchangeService = new ExchangeService(ExchangeVersion.Exchange2010, TimeZoneInfo.Local)
{
PreferredCulture = new CultureInfo("en-US"),
Url = new Uri(EmailServer),
UseDefaultCredentials = true
};
Console.WriteLine("exchangeService.TimeZone.DisplayName: " + exchangeService.TimeZone.DisplayName);
var startDate = DateTime.Today;
var endDate = startDate.AddHours(1);
//Create initial appointment
var appointment = new Appointment(exchangeService)
{
Subject = "Testing Appointments",
Body = "Testing Appointments Body",
Location = "Test Location",
LegacyFreeBusyStatus = LegacyFreeBusyStatus.Busy,
Sensitivity = Sensitivity.Private,
Start = startDate,
End = endDate
};
appointment.OptionalAttendees.Add(EmailAddress);
appointment.Save(SendInvitationsMode.SendOnlyToAll);
Console.WriteLine("exchangeService.TimeZone.DisplayName: " + exchangeService.TimeZone.DisplayName);
var appointmentId = appointment.Id;
Console.WriteLine("Pause to check inbox 'When' value on invite");
Console.ReadLine();
appointment = Appointment.Bind(exchangeService, appointmentId);
appointment.Load(new PropertySet(PropertySet.FirstClassProperties)
{
AppointmentSchema.StartTimeZone,
AppointmentSchema.EndTimeZone,
AppointmentSchema.TimeZone
});
appointment.Body = "Body Updated Successfully";
appointment.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendOnlyToAll);
Console.WriteLine("exchangeService.TimeZone.DisplayName: " + exchangeService.TimeZone.DisplayName);
Console.WriteLine("appointment.StartTimeZone.DisplayName: " + appointment.StartTimeZone.DisplayName);
Console.WriteLine("appointment.EndTimeZone.DisplayName: " + appointment.EndTimeZone.DisplayName);
Console.WriteLine("appointment.TimeZone: " + appointment.TimeZone);
Console.WriteLine();
Console.WriteLine("Pause to check updated inbox 'When' value on invite");
Console.ReadLine();
appointment = Appointment.Bind(exchangeService, appointmentId);
appointment.Load(new PropertySet(PropertySet.FirstClassProperties)
{
AppointmentSchema.StartTimeZone,
AppointmentSchema.EndTimeZone,
AppointmentSchema.TimeZone
});
Console.WriteLine("exchangeService.TimeZone.DisplayName: " + exchangeService.TimeZone.DisplayName);
Console.WriteLine("appointment.StartTimeZone.DisplayName: " + appointment.StartTimeZone.DisplayName);
Console.WriteLine("appointment.EndTimeZone.DisplayName: " + appointment.EndTimeZone.DisplayName);
Console.WriteLine("appointment.TimeZone: " + appointment.TimeZone);
appointment.CancelMeeting();
Console.WriteLine("Appointment Deleted");
Console.ReadLine();
}
}
}
The Results of the above code
Initial Invite (Correct Timezone)
Updated Appointment (Incorrect Timezone in body)
Appointment Cancellation (Incorrect Timezone in body)
Console Result of code provided
What I'm Looking For
I do not need this additional "When" (underlined in red in pictures above) to be appended to the body of the invite. Either I would like to completely remove it (preferred) or I would like to correct it in any updates.
It appears that the issue is a bug in the EWS 2.2.0 DLL, the timezone SOAP headers are not being added to the Update() and CancelMeeting() Exchange transactions. The code below resolves this issue by manually appending the correct header.
For Update():
exchangeService.OnSerializeCustomSoapHeaders += service_OnSerializeCustomSoapHeaders;
appointment.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendOnlyToAll);
exchangeService.OnSerializeCustomSoapHeaders -= service_OnSerializeCustomSoapHeaders;
For CancelMeeting():
exchangeService.OnSerializeCustomSoapHeaders += service_OnSerializeCustomSoapHeaders;
appointment.CancelMeeting();
exchangeService.OnSerializeCustomSoapHeaders -= service_OnSerializeCustomSoapHeaders;
Event Implementation:
static void service_OnSerializeCustomSoapHeaders(XmlWriter writer)
{
writer.WriteRaw(Environment.NewLine + " <t:TimeZoneContext><t:TimeZoneDefinition Id=\"" + TimeZoneInfo.Local.StandardName + "\"/></t:TimeZoneContext>" + Environment.NewLine);
}
Related
I have created a calender.ics file attachment to an email being sent to a user. The user then can add the appointment details to their calendar(whichever they choose Outlook, Google calendar, Yahoo! ect.). The .ics file works beautifully if I click on the attachment and download the file, but I've endlessly searched for a decent example where there is a "Add to Calendar" hyperlink or how to go about it with no real success. Is there way to accomplish this in C#? Am I missing something? This is my first stab at Icalender and I'm using StringBuilder() to build out the .ICS file.
var customerMailMessage = new MailMessage(Store.LocationEmail, CustomerEmail);
customerMailMessage.Subject = "Appointment Request For " + ServiceDate.ToShortDateString();
customerMailMessage.Body = body.ToString();
customerMailMessage.IsBodyHtml = true;
//Start of calender invite
DateTime ServiceTime = ServiceDate.Date.Add(StartTime.TimeOfDay);
DateTime DateStart = Convert.ToDateTime(ServiceTime);
string DateFormat = "yyyyMMddTHHmmssZ";
string now = DateTime.Now.ToUniversalTime().ToString(DateFormat);
string Location = string.Format(Store.LocationAddress1 + " " + Store.LocationCity + " " + Store.LocationState + " " + Store.LocationZip);
string Summary = "Calendar reminder";
string Description = "Your appointment details on: " + DateStart;
string FileName = "ServiceAppointment.ics";
Guid guid = Guid.NewGuid();
StringBuilder sb = new StringBuilder();
sb.AppendLine("BEGIN:VCALENDAR");
sb.AppendLine("VERSION:2.0");
sb.AppendLine("PRODID:Domain.com");
sb.AppendLine("CALSCALE:GREGORIAN");
sb.AppendLine("METHOD:REQUEST");
sb.AppendLine("BEGIN:VEVENT");
sb.AppendLine("UID:" + guid);
sb.AppendLine(string.Format("ORGANIZER:MAILTO:{0}", retailStore.LocationEmail));
sb.AppendLine(string.Format("ATTENDEE;CN='{0}, {1}';ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE:MAILTO:{2}", CustomerFirstName, CustomerLastName, CustomerEmail));
sb.AppendLine("DTSTART:" + DateStart.ToUniversalTime().ToString(DateFormat));
sb.AppendLine("DTSTAMP:" + now);
sb.AppendLine("SUMMARY: " + Summary);
sb.AppendLine("LOCATION: " + Location);
sb.AppendLine("DESCRIPTION:" + Description);
sb.AppendLine("PRIORITY:5");
sb.AppendLine("TRANSP:OPAQUE");
sb.AppendLine("END:VEVENT");
sb.AppendLine("END:VCALENDAR");
var calendarBytes = Encoding.UTF8.GetBytes(sb.ToString());
System.IO.MemoryStream stream = new System.IO.MemoryStream(calendarBytes);
Attachment attachment = new Attachment(stream, FileName, "text/calendar");
customerMailMessage.Attachments.Add(attachment);
System.Net.Mime.ContentType contype = new System.Net.Mime.ContentType("text/calendar");
contype.Parameters.Add("method", "REQUEST");
contype.Parameters.Add("name", "ServiceAppointment.ics");
var emailProvider = new EmailProvider();
emailProvider.Send(customerMailMessage, "Request Appointment - Customer - " + CreatedBySource);
Short-answer: A link in the body of the message would not invoke the method[s] on the server-side to convert the ICS file into a Calendar Item for the user's mailbox; especially, considering each platform mentioned (Exchange, Google Mail, Yahoo Mail, etc.) has it's own implementation for doing so (whether via web or client).
As an example, you can read Microsoft's Open Specification for converting an iCal to an Appointment item here.
I am building an application that accesses meetings from Exchange. I'm using the code provided by Microsoft in their EWS documentation. The issue is I need to access a specific calendar. Say, I create 2 calendars apart from the default one. When I access meetings using this code, I only get meetings from the default calendar. I want to access meetings from a specific calendar. How can I do this?
Thanks for the help.
// Initialize values for the start and end times, and the number of appointments to retrieve.
DateTime startDate = DateTime.Now;
DateTime endDate = startDate.AddDays(30);
const int NUM_APPTS = 5;
// 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();
}
I think your problem is that you are using WellKnownFolderName.Calendar:
CalendarFolder calendar = CalendarFolder.Bind(service, WellKnownFolderName.Calendar, new PropertySet());
Instead you should use the FolderId of the calendar you have created. To get the id of the folder (calendar) you can use the code similar to this (found in answer: https://stackoverflow.com/a/24133821/1037864)
ExtendedPropertyDefinition PR_Folder_Path = new ExtendedPropertyDefinition(26293, MapiPropertyType.String);
PropertySet psPropSet = new PropertySet(BasePropertySet.FirstClassProperties);
psPropSet.Add(PR_Folder_Path);
FolderId rfRootFolderid = new FolderId(WellKnownFolderName.Root, mbMailboxname);
FolderView fvFolderView = new FolderView(1000);
fvFolderView.Traversal = FolderTraversal.Deep;
fvFolderView.PropertySet = psPropSet;
SearchFilter sfSearchFilter = new SearchFilter.IsEqualTo(FolderSchema.FolderClass, "IPF.Appointment");
FindFoldersResults ffoldres = service.FindFolders(rfRootFolderid, sfSearchFilter, fvFolderView);
if (ffoldres.Folders.Count > 0) {
foreach (Folder fld in ffoldres.Folders) {
Console.WriteLine(fld.Id.ToString() + " " + fld.DisplayName);
}
}
Take the value from fld.Id and use instead of WellKnownFolderName.Calendar.
(I don't have an Exchange server available right now to try this out but I hope you get the idea and that my code i correct.)
I am trying to create an outlook appointment ICS file using C# Asp.net. Below is a sample code that I got from the internet, but it is missing Subject, and also attendees. How can I include that in the code? For example Meeting Subject is: "Finance meeting" Attendees is: Sam#mycompany.com, Andy#mycompany.com, Lisa#mycompany.com
public string MakeHourEvent(string subject, string location, DateTime date, string startTime, string endTime)
{
string filePath = string.Empty;
string path = HttpContext.Current.Server.MapPath(#"\iCal\");
filePath = path + subject + ".ics";
writer = new StreamWriter(filePath);
writer.WriteLine("BEGIN:VCALENDAR");
writer.WriteLine("VERSION:2.0");
writer.WriteLine("PRODID:-//hacksw/handcal//NONSGML v1.0//EN");
writer.WriteLine("BEGIN:VEVENT");
string startDateTime = GetFormatedDate(date)+"T"+GetFormattedTime(startTime);
string endDateTime = GetFormatedDate(date) + "T" + GetFormattedTime(endTime);
writer.WriteLine("DTSTART:" + startDateTime);
writer.WriteLine("DTEND:" + endDateTime);
writer.WriteLine("SUMMARY:" + subject);
writer.WriteLine("LOCATION:" + location);
writer.WriteLine("END:VEVENT");
writer.WriteLine("END:VCALENDAR");
writer.Close();
return filePath;
}
The subject is in the SUMMARY: parameter. DESCRIPTION: is used for the "body".
Attendees are added using ATTENDEE: property, i.e.
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN="John Smith"
;X-NUM-GUESTS=0:mailto:john.smith#domain.com
I am currently developing an app which checks one or more users calendars for appointments/meetings under a specific category.
Being new to working with EWS, i have been trying to find a solution as to get a Calendar item (appointment or meeting) by Category or determine if an appointment has a specific category. I currently have the following code so far (exService = ExchangeService object):
foreach (Appointment a in exService.FindItems(WellKnownFolderName.Calendar, new ItemView(int.MaxValue)))
{
//Need to check if appointment has category f.x.: "SMS"
}
Does anybody know a way to acheive this?
Thanks
When your querying Appointments you want to use FindAppointments and a calender-view rather then using FindItems this will ensure that any recurring appointments are expanded eg see https://msdn.microsoft.com/en-us/library/office/dn495614(v=exchg.150).aspx
to use categories all you need to do is something like
DateTime startDate = DateTime.Now;
DateTime endDate = startDate.AddDays(60);
const int NUM_APPTS = 1000;
// 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,AppointmentSchema.Categories);
// 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)
{
if (a.Categories.Contains("Green"))
{
Console.Write("Subject: " + a.Subject.ToString() + " ");
Console.Write("Start: " + a.Start.ToString() + " ");
Console.Write("End: " + a.End.ToString());
}
Console.WriteLine();
}
Does anybody know if its possible to save the windows event logs from a given time interval as a text file with C#? For example say I want to save the System event logs from between 10 - 11 am in a text file. If it is possible does anybody have a link to a good tutorial or maybe a code snippet that could get me going? I have searched online but cant get what I am looking for.
http://www.dreamincode.net/forums/topic/93268-working-with-the-system-event-log-with-c%23-intro/
Just adding info for others on how to filter event logs by time range as part of the WMI query.
Note that 'TimeGenerated' is when events happen and 'TimeWritten' when they are logged.
The 'RecordNumber' is a unique index, useful for preventing collision or duplicate logging.
There is System.Management.ManagementDateTimeConverter that converts between C# DateTime and the WMI CIM_DATETIME format.
But be aware it makes the UTC CIM into a LOCAL DateTime while leaving Kind Unspecified, so set Kind afterwards to avoid headaches!
This is an example for grabbing security failures ( to track lockouts ) in the last 30 minutes:
private void SearchEventViewer(string computerName, string userName, string userPass)
{
var scope = CreateManagementScope(computerName, userName, userPass);
var startTime = ManagementDateTimeConverter.ToDmtfDateTime(DateTime.UtcNow.AddMinutes(-30));
var query = new SelectQuery("SELECT * FROM Win32_NTLogEvent WHERE Logfile = 'Security' AND EventType = '5' AND TimeGenerated > '" + startTime + "'");
using (var searcher = new ManagementObjectSearcher(scope, query))
{
var result = searcher.Get();
foreach (var item in result)
{
var eventTimeLocal = DateTime.SpecifyKind(ManagementDateTimeConverter.ToDateTime(item["TimeGenerated"].ToString()), DateTimeKind.Local);
var eventTimeUtc = eventTimeLocal.ToUniversalTime();
var eventDetails = item["Message"].ToString().Replace("\r\n\r\n", "\r\n");
eventDetails += "\r\nEventCode: " + item["EventCode"];
eventDetails += "\r\nCatogory: " + item["Category"];
eventDetails += "\r\nRecord Number: " + item["RecordNumber"];
eventDetails += "\r\nLocal Time: " + eventTimeLocal.ToString("yyyy-MM-dd HH:mm:ss");
// Do something...
}
}
}
private ManagementScope CreateManagementScope(string computerName, string username = "", string password = "")
{
var managementPath = #"\\" + computerName + #"\root\cimv2";
var scope = new ManagementScope(managementPath);
if (username != "" && password != "")
{
scope.Options = new ConnectionOptions
{
Username = username,
Password = password,
Impersonation = ImpersonationLevel.Impersonate,
Authentication = AuthenticationLevel.PacketPrivacy
};
}
return scope;
}