Creating a Recurring Appointment With MAPI

[This is now documented here: ]

This is the first of a three part series documenting the MFCMAPI sample add-in CreateOutlookItemsAddin. We’re in the process of updating the MAPI documentation and these articles are a preview of some of the new content. This article assumes you have downloaded the add-in from the above link and are running it inside the current version of MFCMAPI. Feedback on the article and on the code is especially welcome.

Creating a Recurring Appointment

MAPI can be used to create appointments items

To create an appointment item

  1. Open a message store. See Opening a Message Store.
  2. Open a Calendar folder in the message store. See PR_IPM_APPOINTMENT_ENTRYID.
  3. Call the Calendar folder’s IMAPIFolder::CreateMessage method to create the new message.
  4. Follow the guidance in [MS-OXOCAL].pdf to set the appropriate appointment related properties.
  5. Save the message.

MFCMAPI demonstrates these steps with the AddAppointment function. AddAppointment takes numerous parameters from the Add Appointment dialog box that is displayed when the user selects Add Appointment on the Addins menu in MFCMAPI. The DisplayAddAppointmentDialog method in Appointments.cpp displays the dialog box and passes values from the dialog box to the AddAppointment method. The DisplayAddAppointmentDialog method does not relate directly to creating an appointment item using MAPI, so it is not listed here. Note that MFCMAPI does not ensure a calendar folder has been selected. Creating calendar items in non calendar folders may lead to undefined behavior.

The AddAppointment method is listed below. Note that the first parameter passed to the AddAppointment method is a pointer to an IMAPIFolder interface. Given lpFolder that represents an IMAPIFolder interface, the code calls IMAPIFolder::CreateMessage. The CreateMessage method returns a success code and a pointer to a pointer to an IMessage interface. Most of the AddAppointment function code handles the work of property setting in preparation for IMAPIProp::SetProps. If the SetProps call succeeds, IMAPIProp::SaveChanges commits the changes to the store and creates a new calendar item.

AddAppointment sets a number of named properties. See Using MAPI to Create Outlook 2007 Items for a discussion of named properties and how they are created. Since the named properties used for appointment items occupy multiple property sets, care must be taken when building parameters to pass to GetIDsFromNames.

AddAppointment uses helper functions to build a structure for various appointment related properties. BuildTimeZoneStruct and BuildTimeZoneDefinition are used to build the time zone related properties discussed in [MS-OXOCAL].pdf, sections through BuildGlobalObjectID is used to build a structure for the PidLidGlobalObjectID and PidLidCleanGlobalObjectId properties discussed in [MS-OXOCAL].pdf, sections and The PidLidAppointmentRecur property is built using BuildWeeklyAppointmentRecurrencePattern. The structure it builds is documented in [MS-OXOCAL].pdf, section, PidLidAppointmentRecur. Note that while a large variety of appointment recurrence patterns are possible, BuildWeeklyAppointmentRecurrencePattern only builds a weekly appointment recurrence pattern. It also makes a number of assumptions, such as the calendar type (Gregorian), the first day of the week (Sunday), and number of modified or deleted instances (none). A more general purpose appointment recurrence pattern creation function would need to accept these sorts of variables as parameters.

Code Snippet

HRESULT AddAppointment(LPMAPIFOLDER lpFolder,
					   SYSTEMTIME* lpstStartDateLocal,
					   SYSTEMTIME* lpstEndDateLocal,
					   SYSTEMTIME* lpstStartFirstUST,
					   SYSTEMTIME* lpstEndFirstUST,
					   SYSTEMTIME* lpszClipStartUST,
					   SYSTEMTIME* lpstClipEndUST,
					   SYSTEMTIME* lpstFirstDOW,
					   DWORD dwStartOffsetLocal,
					   DWORD dwEndOffsetLocal,
					   DWORD dwPeriod,
					   DWORD dwOccurrenceCount,
					   DWORD dwPatternTypeSpecific,
					   ULONG ulDuration,
					   LPWSTR szSubject,
					   LPWSTR szLocation,
					   LPWSTR szPattern)
	if (!lpFolder) return MAPI_E_INVALID_PARAMETER;
	LPMESSAGE lpMessage = 0;
	// create a message and set its properties
	hRes = lpFolder->CreateMessage(0,
	if (SUCCEEDED(hRes))
		MAPINAMEID  rgnmid[ulAppointmentProps];
		LPMAPINAMEID rgpnmid[ulAppointmentProps];
		LPSPropTagArray lpNamedPropTags = NULL;

		ULONG i = 0;
		for (i = 0 ; i < ulAppointmentProps ; i++)
			if (i < ulFirstMeetingProp)
				rgnmid[i].lpguid = (LPGUID)&PSETID_Appointment;
			elseif (i < ulFirstCommonProp)
				rgnmid[i].lpguid = (LPGUID)&PSETID_Meeting;
				rgnmid[i].lpguid = (LPGUID)&PSETID_Common;
			rgnmid[i].ulKind = MNID_ID;
			rgnmid[i].Kind.lID = aulAppointmentProps[i];
			rgpnmid[i] = &rgnmid[i];

		hRes = lpFolder->GetIDsFromNames(
			(LPMAPINAMEID*) &rgpnmid, 
		if (SUCCEEDED(hRes) && lpNamedPropTags)
			// Since we know in advance which props
			// we'll be setting, we can statically
			// declare most of the structures involved
			// and save expensive MAPIAllocateBuffer calls
			// For brevity, code to set most spvProps
			// has been removed. For the complete listing, see
			// AddAppointments in Appointments.cpp

			spvProps[p_PR_SUBJECT_W].ulPropTag          = PR_SUBJECT_W;
			spvProps[p_PR_START_DATE].ulPropTag         = PR_START_DATE;
			spvProps[p_PR_END_DATE].ulPropTag           = PR_END_DATE;
			spvProps[p_PR_MESSAGE_CLASS_W].ulPropTag    = PR_MESSAGE_CLASS_W;
			spvProps[p_PR_ICON_INDEX].ulPropTag         = PR_ICON_INDEX;
			spvProps[p_PR_MESSAGE_FLAGS].ulPropTag      = PR_MESSAGE_FLAGS;

			spvProps[p_PidLidAppointmentSequence].Value.l = 0;
			spvProps[p_PidLidBusyStatus].Value.l = olBusy;
			spvProps[p_PidLidLocation].Value.lpszW = szLocation;
			spvProps[p_PidLidAppointmentDuration].Value.l = ulDuration;
			spvProps[p_PidLidAppointmentColor].Value.l = 0; // No color
			spvProps[p_PidLidResponseStatus].Value.l = respNone;
			spvProps[p_PidLidRecurring].Value.b = true;

			SYSTEMTIME stStandard = {0};
			stStandard.wMonth = 0xB;
			stStandard.wDay = 0x1;
			stStandard.wHour = 0x2;
			SYSTEMTIME stDaylight = {0};
			stDaylight.wMonth = 0x3;
			stDaylight.wDay = 0x2;
			stDaylight.wHour = 0x2;
			hRes = BuildTimeZoneStruct(
			spvProps[p_PidLidTimeZoneDescription].Value.lpszW = L"(GMT-05:00) Eastern Time (US & Canada)";
			SYSTEMTIME stRule1Standard = {0};
			stRule1Standard.wMonth = 0xA;
			stRule1Standard.wDay = 0x5;
			stRule1Standard.wHour = 0x2;
			SYSTEMTIME stRule1Daylight = {0};
			stRule1Daylight.wMonth = 0x4;
			stRule1Daylight.wDay = 0x1;
			stRule1Daylight.wHour = 0x2;
			if (SUCCEEDED(hRes)) hRes = BuildTimeZoneDefinition(
				L"Eastern Standard Time",
				0, // TZRule Flags
				2006, // wYear
				300, // lbias
				0, // lStandardBias,
				(DWORD)-60, // lDaylightBias,
				&stRule1Standard, // stStandardDate
				&stRule1Daylight, // stDaylightDate
				2007, // wYear
				300, // lbias
				0, // lStandardBias,
				(DWORD)-60, // lDaylightBias,
				&stStandard, // stStandardDate
				&stDaylight, // stDaylightDate
			spvProps[p_PidLidAppointmentTimeZoneDefinitionStartDisplay].Value.bin.cb  = 
			spvProps[p_PidLidAppointmentTimeZoneDefinitionStartDisplay].Value.bin.lpb = 
			spvProps[p_PidLidAppointmentTimeZoneDefinitionEndDisplay].Value.bin.cb  = 
			spvProps[p_PidLidAppointmentTimeZoneDefinitionEndDisplay].Value.bin.lpb = 
			if (SUCCEEDED(hRes)) hRes = BuildWeeklyAppointmentRecurrencePattern(
			spvProps[p_PidLidRecurrenceType].Value.l = rectypeWeekly;
			spvProps[p_PidLidRecurrencePattern].Value.lpszW = szPattern;
			spvProps[p_PidLidIsRecurring].Value.b = true;
			if (SUCCEEDED(hRes)) hRes = BuildGlobalObjectId(
			spvProps[p_PidLidCleanGlobalObjectId].Value.bin.cb  = 
			spvProps[p_PidLidCleanGlobalObjectId].Value.bin.lpb = 
			spvProps[p_PidLidSideEffects].Value.l = 
				seOpenToDelete | seOpenToCopy | seOpenToMove | seCoerceToInbox | seOpenForCtxMenu;

			spvProps[p_PR_SUBJECT_W].Value.lpszW = szSubject;
			spvProps[p_PR_MESSAGE_CLASS_W].Value.lpszW = L"IPM.Appointment";
			spvProps[p_PR_ICON_INDEX].Value.l = 0x00000401; // Recurring Appointment
			if (SUCCEEDED(hRes)) hRes = BuildConversationIndex(
			spvProps[p_PR_MESSAGE_FLAGS].Value.l = MSGFLAG_READ;

			if (SUCCEEDED(hRes)) hRes = lpMessage->SetProps(NUM_PROPS, spvProps, NULL);
			if (SUCCEEDED(hRes))
				hRes = lpMessage->SaveChanges(FORCE_SAVE);
			if (spvProps[p_PidLidTimeZoneStruct].Value.bin.lpb)
				delete[] spvProps[p_PidLidTimeZoneStruct].Value.bin.lpb;
			if (spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.lpb)
				delete[] spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.lpb;
			// Do not delete p_PidLidAppointmentTimeZoneDefinitionStartDisplay,
			// it was borrowed from p_PidLidAppointmentTimeZoneDefinitionStartDisplay
			// Do not delete p_PidLidAppointmentTimeZoneDefinitionEndDisplay,
			// it was borrowed from p_PidLidAppointmentTimeZoneDefinitionStartDisplay
			if (spvProps[p_PidLidAppointmentRecur].Value.bin.lpb)
				delete[] spvProps[p_PidLidAppointmentRecur].Value.bin.lpb;
			if (spvProps[p_PidLidGlobalObjectId].Value.bin.lpb)
				delete[] spvProps[p_PidLidGlobalObjectId].Value.bin.lpb;
			// Do not delete p_PidLidCleanGlobalObjectId,
			// it was borrowed from p_PidLidGlobalObjectId
			if (spvProps[p_PR_CONVERSATION_INDEX].Value.bin.lpb) 
				delete[] spvProps[p_PR_CONVERSATION_INDEX].Value.bin.lpb;
	if (lpMessage) lpMessage->Release();
	return hRes;
Comments (6)

  1. This is the second part of a three part series documenting the MFCMAPI sample add-in CreateOutlookItemsAddin

  2. This is the third part of a three part series documenting the MFCMAPI sample add-in CreateOutlookItemsAddin

  3. Henry Gusakovsky says:

    I have a question about ModifiedInstanceDates field in the RecurrencePattern struct.

    Snippet from MS-OXOCAL updated.


    "This is the array of the original instance date of modified instances. There is exactly one element for each modified instance and every modified instance MUST be represented in this array. Every modified instance MUST also have an entry in the array of DeletedInstanceDates. The count of the array MUST be equal to ModifiedInstanceCount field. Each ModifiedInstanceDate is stored as the number of minutes between midnight of the specified day and midnight, January 1, 1601, in the timezone specified by PidLidTimeZoneStruct. The values in this list MUST be ordered from earliest to latest. There SHOULD NOT be duplicate entries in this list."

    F.e. I create an exception for 10/9/2008 and I move this instance to 10/10/2008 according to this snippet i need to put there 10/9/2008 (in minutes) because it  is original date of this instance. But Outlook puts there 10/9/2008 which is  date for modified instance. So it seems there is some misunderstanding about docs and real data.

  4. Stephen Griffin says:

    Henry – please take this feedback to the forum listed in the PDF. That’ll get it in front of the people who need to see it. I’m assuming you meant to say "But Outlook puts there 10/10/2008 which is the date for modified instance", which is what I would expect to be in this array. I believe the error is in the first sentence, which should have read "This is the array of the modified instance dates of modified instances".

  5. Henry Gusakovsky says:

    Yea, You are right.

    I’ve posted this to the forum.

  6. Manish Patel says:


    Great work man,

    I want to set all day event property of Appointment.

    I am using property PidLidAppointmentSubType(0x8215) for that, But no result.

    Do you have any idea about it?

    Please post code block, if you have any.


    Manish Patel