Creating a Recurring Appointment With MAPI

[This is now documented here: https://msdn.microsoft.com/en-us/library/cc963259.aspx ]

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 2.2.1.39 through 2.2.1.43. BuildGlobalObjectID is used to build a structure for the PidLidGlobalObjectID and PidLidCleanGlobalObjectId properties discussed in [MS-OXOCAL].pdf, sections 2.2.1.27 and 2.2.1.28. The PidLidAppointmentRecur property is built using BuildWeeklyAppointmentRecurrencePattern. The structure it builds is documented in [MS-OXOCAL].pdf, section 2.2.1.44, 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;
    HRESULT hRes = S_OK;
    LPMESSAGE lpMessage = 0;
    // create a message and set its properties
    hRes = lpFolder->CreateMessage(0,
        0,
        &lpMessage);
    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;
        else
            rgnmid[i].lpguid = (LPGUID)&PSETID_Common;
        rgnmid[i].ulKind = MNID_ID;
        rgnmid[i].Kind.lID = aulAppointmentProps[i];
        rgpnmid[i] = &rgnmid[i];
    }

    hRes = lpFolder->GetIDsFromNames(
        ulAppointmentProps, 
        (LPMAPINAMEID*) &rgpnmid, 
        NULL, 
        &lpNamedPropTags);
    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_CONVERSATION_INDEX].ulPropTag = PR_CONVERSATION_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;
        SystemTimeToFileTime(lpstStartFirstUST,&spvProps[p_PidLidAppointmentStartWhole].Value.ft);
        SystemTimeToFileTime(lpstEndFirstUST,&spvProps[p_PidLidAppointmentEndWhole].Value.ft);
        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;
        SystemTimeToFileTime(lpszClipStartUST,&spvProps[p_PidLidClipStart].Value.ft);
        SystemTimeToFileTime(lpstClipEndUST,&spvProps[p_PidLidClipEnd].Value.ft);

        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(
            300,
            0,
            (DWORD)-60,
            &stStandard,
            &stDaylight,
            &spvProps[p_PidLidTimeZoneStruct].Value.bin.cb,
            &spvProps[p_PidLidTimeZoneStruct].Value.bin.lpb);
        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
            TZRULE_FLAG_EFFECTIVE_TZREG, // TZRule Flags
            2007, // wYear
            300, // lbias
            0, // lStandardBias,
            (DWORD)-60, // lDaylightBias,
            &stStandard, // stStandardDate
            &stDaylight, // stDaylightDate
            &spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.cb,
            &spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.lpb);
        spvProps[p_PidLidAppointmentTimeZoneDefinitionStartDisplay].Value.bin.cb  = 
            spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.cb;
        spvProps[p_PidLidAppointmentTimeZoneDefinitionStartDisplay].Value.bin.lpb = 
            spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.lpb;
        spvProps[p_PidLidAppointmentTimeZoneDefinitionEndDisplay].Value.bin.cb  = 
            spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.cb;
        spvProps[p_PidLidAppointmentTimeZoneDefinitionEndDisplay].Value.bin.lpb = 
            spvProps[p_PidLidAppointmentTimeZoneDefinitionRecur].Value.bin.lpb;
        if (SUCCEEDED(hRes)) hRes = BuildWeeklyAppointmentRecurrencePattern(
            lpstStartDateLocal,
            lpstEndDateLocal,
            lpstFirstDOW,
            dwStartOffsetLocal,
            dwEndOffsetLocal,
            dwPeriod,
            dwOccurrenceCount,
            dwPatternTypeSpecific,
            &spvProps[p_PidLidAppointmentRecur].Value.bin.cb,
            &spvProps[p_PidLidAppointmentRecur].Value.bin.lpb);
        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_PidLidGlobalObjectId].Value.bin.cb,
            &spvProps[p_PidLidGlobalObjectId].Value.bin.lpb);
        spvProps[p_PidLidCleanGlobalObjectId].Value.bin.cb  = 
            spvProps[p_PidLidGlobalObjectId].Value.bin.cb;
        spvProps[p_PidLidCleanGlobalObjectId].Value.bin.lpb = 
            spvProps[p_PidLidGlobalObjectId].Value.bin.lpb;
        SystemTimeToFileTime(lpstStartFirstUST,&spvProps[p_PidLidCommonStart].Value.ft);
        SystemTimeToFileTime(lpstEndFirstUST,&spvProps[p_PidLidCommonEnd].Value.ft);
        spvProps[p_PidLidSideEffects].Value.l = 
            seOpenToDelete | seOpenToCopy | seOpenToMove | seCoerceToInbox | seOpenForCtxMenu;

        spvProps[p_PR_SUBJECT_W].Value.lpszW = szSubject;
        SystemTimeToFileTime(lpstStartFirstUST,&spvProps[p_PR_START_DATE].Value.ft);
        SystemTimeToFileTime(lpstEndFirstUST,&spvProps[p_PR_END_DATE].Value.ft);
        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_CONVERSATION_INDEX].Value.bin.cb,
            &spvProps[p_PR_CONVERSATION_INDEX].Value.bin.lpb);
        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;
    }
    MAPIFreeBuffer(lpNamedPropTags);
}
if (lpMessage) lpMessage->Release();
return hRes;

}