|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn this article you will learn how to implement a C++ library that will help you to use Extended MAPI functions that aren't available through the Outlook Object Model. In the past, you had to buy an additional library or deal with COM-technology and create this native DLL by yourself. Another way was to reinvent the wheel and define all interfaces again on the .NET side. This would allow you to use these interfaces directly from C# or VB.NET by marshalling the .NET structures to unmanaged functions and structures. Here comes a handy C++ library where you can easily mix managed and native code in one environment. Benefits
Drawbacks
BackgroundThe Microsoft Outlook Object Model (OOM) is powerful and provides access to many features that use and manipulate the data stored in Outlook and the Exchange Server. However, every serious Outlook developer comes to a point where he or she needs a special function, property or field that is not available through the OOM or is blocked due to security restrictions. In the past, you had to use an external COM library like the highly recommended:
While these libraries are very powerful and flexible because they can be used from every programming language, they are still additional libraries that have to be registered and deployed with your application. This can sometimes be problematic. When using Extended MAPI from .NET, there exists another library called:
Set up the solutionBefore you can start hacking into Extended MAPI, you have to install the minimum requirements on your development machine. Prerequisites
Create a solutionTo demonstrate how it works behind the scenes, you start with a simple Windows Forms application that creates a new email and adds an additional SMTP header to it. In this sample, you will find a C# and a VB.NET solution respectively. So, open Visual Studio, choose the language of your choice and create an application that looks like this:
Let's say you are an Outlook programmer and you want to use the Outlook Object Model. You naturally have to go to the project references and add the following COM libraries as reference to your project:
When you have finished with adding the references to your project, you can import the namespaces to the code files like this: Imports Office = Microsoft.Office.Core
Imports Outlook = Microsoft.Office.Interop.Outlook
using Office = Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook;
The Outlook thingThe goal is to create a new email and attach an SMTP header to it. So, first we look at how to create a new email with the Outlook Object Model: Private Sub btnSend_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnSend.Click
' get the Outlook Application Object
Dim outlookApplication As Outlook.Application = New Outlook.Application()
' get the namespace object
Dim olNamespace As Outlook.NameSpace = _
okApplication.GetNamespace("MAPI")
' logon to Session, here we use an already opened Outlook
olNamespace.Logon(, , True, True)
' create a new email and fill it with the info provided in the Form
Dim newMail As Outlook.MailItem = _
lookApplication.CreateItem(Outlook.OlItemType.olMailItem)
newMail.To = tbxRecipientAddress.Text
newMail.Subject = tbxSubject.Text
newMail.Body = tbxMessage.Text
newMail.Send()
newMail = Nothing
' logoff from namespace (session)
olNamespace.Logoff()
'release reference to Outlook object
outlookApplication = Nothing
' Let the Garbagecollector do his work
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
C++private void btnSend_Click(object sender, EventArgs e)
{
object missing = System.Reflection.Missing.Value;
// get the Outlook Application Object
Outlook.Application outlookApplication = new Outlook.Application();
// get the namespace object
Outlook.NameSpace nameSpace = outlookApplication.GetNamespace("MAPI");
// Logon to Session, here we use an already opened Outlook
nameSpace.Logon(missing, missing, true, true);
// create a new email and fill it with the info provided in the Form
Outlook.MailItem newMail = _
lookApplication.CreateItem(Outlook.OlItemType.olMailItem);
newMail.To = tbxRecipientAddress.Text;
newMail.Subject = tbxSubject.Text;
newMail.Body = tbxMessage.Text;
newMail.Send();
newMail = null;
// logoff from namespace (session)
olNamespace.Logoff();
//release reference to Outlook object
outlookApplication = Nothing;
// Let the Garbagecollector do his work
GC.Collect();
GC.WaitForPendingFinalizers();
}
This small snippet should simply send out an Email to the recipient that you provided in the To: field. The mixed C++ libraryNow you must add a new project to the solution. Choose File|Add|New Project and select C++ CLR Class Library.
I named the library
The next step is to add the MAPI C++ header files to your project. Open the stdafx.h file and add the following headers to it after the #pragma once
#include <MAPIX.h>
#include <MapiUtil.h>
#include <MAPIGuid.h>
#include <MAPITags.h>
Now compile the project and see if it gets compiled successfully. If yes, everything goes well and you can continue. If not, you are probably missing a prerequisite like the Windows SDK containing the C++ header files defined in StdAfx.h. Adding functionalityNow you should have a compiling .NET C++ class library with the linked Extended MAPI library. Go on and start exposing functionality to your .NET projects. Remembering the initial goal, you want to add an SMTP header to an outgoing Outlook email. Now you can continue and provide an interface to the external world that exposes the desired functionality. The header fileC++ classes usually have a header file and a code file. Here in your library, the header file looks like you can see below. You have defined a .NET class, #pragma once
using namespace System;
using namespace System::Runtime::InteropServices;
namespace MAPIConcubine
{
///
/// The Fabric Class defines Methods and Functions to access
/// Extended MAPI Functions from .NET
///
public ref class Fabric
{
// private methods and variables
private:
// destructor
~Fabric();
///
/// Used to retrieve a human readable Errormessage from a
/// MAPI Errorcode.
///
/// [in]dw - the error number returned from MAPI
/// [returns] Returns the Errormessage to the given Errornumber.
String^ GetErrorText(DWORD dw);
// All public visible methods
public:
// construction code
Fabric();
///
/// Adds an InternetHeader to an outgoing Outlook- Email Message
///
/// [in]mapiObject - The MAPIOBJECT property of the Outlook mailItem
/// [in]headerName - The name of the header to add
/// [in]headerValue - The value of the header to add
void AddMessageHeader(Object^ mapiObject, String^ headerName,
String^ headerValue);
};
}
You expose only .NET compliant methods to the outside. This makes it easy to use this library from any .NET project. Now you have defined the interface and can go on to implement some functionality behind it. Let's have a look at the implementation that can be found in the MAPIConcubine.cpp file. The idea is to get the ///
/// Adds an InternetHeader to an outgoing Outlook- Email Message
///
/// [in]mapiObject - The MAPIOBJECT property of the Outlook mailItem
/// [in]headerName - The name of the header to add
/// [in]headerValue - The value of the header to add
void Fabric::AddMessageHeader(Object^ mapiObject, String^ headerName,
String^ headerValue)
{
// Pointer to IUnknown Interface
IUnknown* pUnknown = 0;
// Pointer to IMessage Interface
IMessage* pMessage = 0;
// Pointer to IMAPIProp Interface
IMAPIProp* pMAPIProp = 0;
// a structure for the MANEDProp variable
MAPINAMEID* pNamedProp = 0;
// an array of PropertyTags as result for the GetIDdFromNames Method.
SPropTagArray* lpTags = 0;
// Convert the .Net Strings to unmanaged memory blocks
IntPtr pHeaderName = Marshal::StringToHGlobalUni (headerName);
IntPtr pHeaderValue = Marshal::StringToHGlobalUni (headerValue);
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr)
throw gcnew System::ArgumentNullException
("mapiObject","The MAPIObject must not be null!");
try
{
// retrieve the IUnknon Interface from our MAPIObject
// coming from Outlook.
pUnknown = (IUnknown*)Marshal::GetIUnknownForObject
(mapiObject).ToPointer ();
// try to retrieve the IMessage interface,
// if we don't get it, everything else is senseless.
if ( pUnknown->QueryInterface
(IID_IMessage, (void**)&pMessage) != S_OK)
throw gcnew Exception
("QueryInterface failed on IUnknown for IID_Message");
// try to retrieve the IMAPIProp interface from IMessage Interface,
// everything else is senseless.
if ( pMessage->QueryInterface
(IID_IMAPIProp, (void**)&pMAPIProp) != S_OK)
throw gcnew Exception
("QueryInterface failed on IMessage for IID_IMAPIProp");
// double check, if we wave no pointer, exit...
if (pMAPIProp == 0)
throw gcnew Exception
("Unknown Error on receiving the Pointer to MAPI Properties.");
// A well Google documented ID used to access the SMTP Headers
// of an Outlook Message
GUID magicId = { 0x00020386, 0x0000, 0x0000,
{ 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID),
(LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to access the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName
pNamedProp->ulKind = MNID_STRING;
pNamedProp->Kind.lpwstrName = (LPWSTR) pHeaderName.ToPointer();
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames
(1, &pNamedProp, MAPI_CREATE, &lpTags ) != S_OK)
throw gcnew Exception(String::Format
("Error retrieving GetIDsFromNames: {0}.",headerName) );
// the variable that will be passed to the HrSetOneProp method.
SPropValue value;
// the PROP_TAG macro creates the correct value from the
// value type and the value ID for us
value.ulPropTag = PROP_TAG(PT_UNICODE, PROP_ID
(lpTags->aulPropTag[0]));
// here we pass a Unicode string to the method and
// therefore we set the LPSZ property
value.Value.LPSZ = (LPWSTR) pHeaderValue.ToPointer();
// the HrSetOneProp method is used to set the property
// on the MAPI object
if( HrSetOneProp(pMAPIProp, &value) != S_OK)
throw gcnew Exception(String::Format
("Error setting Header: {0}.",headerName) );
}
catch (Exception^ ex)
{
DWORD dw = GetLastError();
throw gcnew Exception(GetErrorText(dw),ex);
}
finally
{
if (pNamedProp != 0) MAPIFreeBuffer(pNamedProp);
if (lpTags != 0) MAPIFreeBuffer(lpTags);
// Free allocated unmanaged memory
Marshal::FreeHGlobal (pHeaderName);
Marshal::FreeHGlobal (pHeaderValue);
// cleanup all references to COM Objects
if (pMAPIProp!=0) pMAPIProp->Release();
if (pMessage!=0) pMessage->Release();
if (pUnknown!=0) pUnknown->Release();
}
}
The interesting thing is how you implement the #pragma once
#define INITGUID
#define USES_IID_IMAPIProp
#define USES_IID_IMessage
#include <MAPIX.h>
#include <MapiUtil.h>
#include <MAPIGuid.h>
#include <MAPITags.h>
Initialization and cleanupAhh! Wait; you missed something. Whenever you want anything from extended MAPI, you have to initialize it. So, you must add a construction and a cleanup routine to the library. This is how it looks: ///
/// construction code - we can initialize the MAPI Session here
///
Fabric::Fabric ()
{
MAPIInitialize(0);
}
///
/// destructor - cleanup the MAPI session here.
///
Fabric::~Fabric ()
{
MAPIUninitialize();
}
Also, for easier error handling and debugging, you should add a method that returns a human readable error message to the caller. This method could look like the following code: ///
/// This method takes an MAPI error code as parameter (GetLastError())
/// and returns the error message as managed string.
///
/// [in]dw - the MAPI error code.
/// [return] returns the human readable error message as string.
String^ Fabric::GetErrorText(DWORD dw)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
String^ result = Marshal::PtrToStringAuto (IntPtr::IntPtr (lpMsgBuf));
LocalFree(lpMsgBuf);
return result;
}
The usageLet's go over to the .NET clients that should use this library. Open the Windows Forms application -- or even VSTO or Extensibility AddIn -- that you created earlier and add a new reference to it. Go to the Projects tab and select the existing project,
Now you can change the code of your Windows Forms application and add the new functionality to it. That simply looks like this: ' create a new email and fill it with the info provided in the Form
Dim newMail As Outlook.MailItem = _
outlookApplication.CreateItem(Outlook.OlItemType.olMailItem)
' here we use our C++ library
Dim fabric As MAPIConcubine.Fabric = New MAPIConcubine.Fabric()
' we pass the MAPIObject property to our library and the parameters
' note that in VB.Net you can't see the MAPIOBJECT property
' but be sure it is there
fabric.AddMessageHeader(newMail.MAPIOBJECT, tbxHeaderName.Text, _
tbxHeaderValue.Text)
' that's all
fabric = Nothing
newMail.To = tbxRecipientAddress.Text
newMail.Subject = tbxSubject.Text
newMail.Body = tbxMessage.Text
' send out the email
newMail.Send()
C++// create a new email and fill it with the info provided in the Form
Outlook.MailItem newMail = outlookApplication.CreateItem
(Outlook.OlItemType.olMailItem) as Outlook.MailItem;
// here we use our C++ library
MAPIConcubine.Fabric fabric = new MAPIConcubine.Fabric();
// we pass the MAPIObject property to our library and the parameters
// note that in VB.Net you can't see the MAPIOBJECT property
// but be sure it is there
fabric.AddMessageHeader(newMail.MAPIOBJECT, tbxHeaderName.Text,
tbxHeaderValue.Text);
// that's all
fabric = null;
newMail.To = tbxRecipientAddress.Text;
newMail.Subject = tbxSubject.Text;
newMail.Body = tbxMessage.Text;
newMail.Send();
That's all; the rest is a piece of cake. You can check the Internet headers of this email at the recipient mailbox and you will see your own headers there. Within Outlook, you can view it by opening the email and showing the Options menu. Happy coding to all. Reading the headerIn this chapter, you will learn how to retrieve the saved header and how to retrieve all transport message headers. The theory is simple: you use the GUID to access the internet headers and the ///
/// Reads an InternetHeader from an Outlook- Email Message
///
/// [in] mapiObject - the MAPIOBJECT property of the outlook mailItem object.
/// [in] headerName - the name of the header to retrieve.
/// [returns] headerValue - the value of the header with the given name.
String^ Fabric::ReadMessageHeader(Object^ mapiObject, String^ headerName)
{
// Pointer to IUnknown Interface
IUnknown* pUnknown = 0;
// Pointer to IMessage Interface
IMessage* pMessage = 0;
// Pointer to IMAPIProp Interface
IMAPIProp* pMAPIProp = 0;
// a structure for the MANEDProp variable
MAPINAMEID* pNamedProp = 0;
// an array of PropertyTags as result for the GetIDdFromNames Method.
SPropTagArray* lpTags = 0;
// pointer to a structure that receives the result from HrGetOneProp
LPSPropValue lpSPropValue = 0;
// Convert the .Net Strings to unmanaged memory blocks
IntPtr pHeaderName = Marshal::StringToHGlobalUni (headerName);
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr) throw gcnew System::ArgumentNullException (
"mapiObject","The MAPIObject must not be null!");
try
{
// retrive the IUnknon Interface from our
// MAPIObject comming from Outlook.
pUnknown =
(IUnknown*)Marshal::GetIUnknownForObject(mapiObject).ToPointer ();
// try to retrieve the IMessage interface, if
// we don't get it, everything else is sensless.
if ( pUnknown->QueryInterface (IID_IMessage,
(void**)&pMessage) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IUnknown for IID_Message");
// try to retrieve the IMAPIProp interface from
// IMessage Interface, everything else is sensless.
if ( pMessage->QueryInterface (IID_IMAPIProp,
(void**)&pMAPIProp) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IMessage for IID_IMAPIProp");
// double check, if we wave no pointer, exit...
if (pMAPIProp == 0) throw gcnew Exception(
"Unknown Error on receiving the Pointer to MAPI Properties.");
// A well Google documented ID used to
// access the SMTP Headers of an Outlook Message
GUID magicId =
{
0x00020386, 0x0000, 0x0000,
{
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
}
};
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID),
(LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to acces the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName
pNamedProp->ulKind = MNID_STRING;
pNamedProp->Kind.lpwstrName = (LPWSTR) pHeaderName.ToPointer();
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames(1, &pNamedProp,
STGM_READ, &lpTags ) != S_OK)
throw gcnew Exception(String::Format (
"Error retrieving GetIDsFromNames: {0}.",headerName) );
// The PROP_TAG macro creates the correct
// value for Type and PropertyID
ULONG propTag = PROP_TAG(PT_UNICODE, PROP_ID(lpTags->aulPropTag[0]));
// use the HrGetOneProp method to retrieve the profile name property
// the lpPropValue pointer points to the result value
if (HrGetOneProp(pMAPIProp,propTag,&lpSPropValue) != S_OK)
throw gcnew Exception("HrGetOneProp failed for named property !");
// create a managed string from the unmanaged string.
return gcnew String( lpSPropValue->Value.lpszW );
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
The usage is equal to the other method. Create a fabric object and pass Reading the current Outlook profile name
In this lesson, you will learn how to master a common problem to Outlook developers. You will learn how to read the profile name of the current MAPI session. In the Outlook Object Model, there is no way to determine the name of the profile that the user has started. With the following snippet, you can solve this issue. The idea is to get the session object and use the ///
/// Returns the MAPI profilename of the current session.
///
/// [in] mapiObject - the MAPIOBJECT property of the
/// Outlook Application.Session object.
/// [returns] - the name of the currently used profile.
String^ Fabric::GetProfileName(Object^ mapiObject)
{
// the result returned to the calling method
String^ result = nullptr;
// pointer to IUnknown interface
IUnknown* pUnknown = 0;
// pointer to the MAPI session interface
LPMAPISESSION lpMAPISession = 0;
// pointer to a profilesection interface
LPPROFSECT lpProfileSection = 0;
// pointer to a structure that receives the result from HrGetOneProp
LPSPropValue lpSPropValue = 0;
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr)
throw gcnew System::ArgumentNullException ("mapiObject",
"The MAPIObject must not be null!");
try
{
// retrive the IUnknon Interface from our MAPIObject
// comming from Outlook.
pUnknown =
(IUnknown*)Marshal::GetIUnknownForObject(mapiObject).ToPointer ();
// try to retrieve the IMAPISession interface, if
// we don't get it, everything else is sensless.
if ( pUnknown->QueryInterface (IID_IMAPISession,
(void**)&lpMAPISession) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IUnknown for IID_IMAPISession");
// use the OpenProfileSection of the MAPISession
// object to retrieve a pointer to the current
// profilesection interface
if( lpMAPISession->OpenProfileSection(
(LPMAPIUID)GLOBAL_PROFILE_SECTION_MAPIUID,
NULL,STGM_READ, &lpProfileSection) != S_OK)
throw gcnew Exception("OpenProfileSection method failed!");
// use the HrGetOneProp method to retrieve the profile name property
// the lpPropValue pointer points to the result value
if (HrGetOneProp(lpProfileSection,
PR_PROFILE_NAME_W,&lpSPropValue) != S_OK)
throw gcnew Exception(
"HrGetOneProp failed for property PR_PROFILE_NAME_W !");
// create a managed string from the unmanaged string.
return gcnew String( lpSPropValue->Value.lpszW );
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
Now it's easy to get information related to the profile the user is currently logged onto. Just for reference, you implement a method to enumerate all available MAPI profiles for the current user. This information is stored in the registry and so you simply open the registry key 'HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles' and enumerate all sub-keys. There is one sub-key for each profile. ///
/// Returns the current MAPI profilenames available in the system registry.
///
/// [returns] - a string array of all available profilenames.
array<string^,1 />^ Fabric::EnumProfileNames(){
String^ registryPath =
"Software\\Microsoft\\Windows NT\\CurrentVersion" +
"\\Windows Messaging Subsystem\\Profiles";
RegistryKey^ key = nullptr;
try
{
key = Registry::CurrentUser->OpenSubKey ( registryPath );
return key->GetSubKeyNames ();
}
catch(System::Exception^ ex)
{
throw ex;
}
finally
{
key->Close();
}
}
The test application provided in the solution demonstrates how to use this method. Just pass along the // Create an Outlook session
object missing = System.Reflection.Missing.Value;
// get the Outlook Application Object
Outlook.Application outlookApplication = new Outlook.Application();
// get the namespace object
Outlook.NameSpace olNamespace = outlookApplication.GetNamespace("MAPI");
// Logon to Session, here we use the name from
// the comboBox to logon to a specific session.
// If there is already an Outlook instance with
// a Session - then this profile is used.
olNamespace.Logon(comboBox1.Text , missing, true, true);
// Use the fabric to get the current profilename
MAPIConcubine.Fabric fabric = new MAPIConcubine.Fabric();
try
{
// pass along the namespace.MAPIOBJECT to retrieve the profilename
string currentProfileName = fabric.GetProfileName(olNamespace.MAPIOBJECT);
MessageBox.Show(this, String.Format(
"The current profilename was: {0}", currentProfileName));
}
catch (System.Exception ex)
{
MessageBox.Show(this,ex.Message);
}
finally
{
fabric = null;
olNamespace.Logoff();
olNamespace = null;
outlookApplication = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
Set appointment label color
In this section, you will learn how to set and get the label color of an Outlook Appointment Item. With Outlook 2002 and later, you have the option of colorizing your Outlook appointments. See this for more information. In the past, you had to use CDO or one of the external libraries mentioned above. Here is the source code that demonstrates how to use Extended MAPI to accomplish this goal from .NET. In the header file, you define the available colors for the appointment labels: /// Defines the available Colors for AppointmentLabels
enum class AppointmentLabelColor
{
None = 0, // Default Color
Important = 1, // Red
Business = 2, // Blue
Personal = 3, // Green
Vacation = 4, // Grey
Deadline = 5, // Orange
Travel_Required = 6, // Light-Blue
Needs_Preparation = 7, // Grey
Birthday = 8, // violett
Anniversary = 9, // turquoise
Phone_Call = 10 // Yellow
} ;
When you are done with the header file, go on and implement the ///
/// Sets the AppointmentLabelColor for a Outlook- Appointment
///
/// [in] mapiObject - the MAPIOBJECT property of
/// the outlook appointmentItem object.
/// [in] the desired colortype that you wish to set
void Fabric::SetAppointmentLabelColor(Object^ mapiObject,
AppointmentLabelColor color)
{
// Pointer to IUnknown Interface
IUnknown* pUnknown = 0;
// Pointer to IMessage Interface
IMessage* pMessage = 0;
// Pointer to IMAPIProp Interface
IMAPIProp* pMAPIProp = 0;
// a structure for the NAMEDProp variable
MAPINAMEID* pNamedProp = 0;
// an array of PropertyTags as result for the GetIDdFromNames Method.
SPropTagArray* lpTags = 0;
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr) throw gcnew System::ArgumentNullException (
"mapiObject","The MAPIObject must not be null!");
try
{
// retrive the IUnknon Interface from our MAPIObject
// comming from Outlook.
pUnknown =
(IUnknown*)Marshal::GetIUnknownForObject(mapiObject).ToPointer ();
// try to retrieve the IMessage interface, if
// we don't get it, everything else is sensless.
if ( pUnknown->QueryInterface (IID_IMessage,
(void**)&pMessage) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IUnknown for IID_Message");
// try to retrieve the IMAPIProp interface from
IMessage Interface, everything else is sensless.
if ( pMessage->QueryInterface (IID_IMAPIProp,
(void**)&pMAPIProp) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IMessage for IID_IMAPIProp");
// double check, if we wave no pointer, exit...
if (pMAPIProp == 0)
throw gcnew Exception(
"Unknown Error on receiving the Pointer to MAPI Properties.");
// A well Google documented ID used to access an
// appointment color label
GUID magicId =
{
0x00062002, 0x0000, 0x0000,
{
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
}
};
LONG propertyName = 0x8214;
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID),
(LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to acces the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName - we have an ID as Property here
pNamedProp->ulKind = MNID_ID;
pNamedProp->Kind.lID = propertyName;
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames(1, &pNamedProp,
MAPI_CREATE, &lpTags ) != S_OK)
throw gcnew Exception(String::Format (
"Error retrieving GetIDsFromNames: {0}.",propertyName) );
// A structure that is used to pass the value to the property
SPropValue value;
// The PROP_TAG macro creates the correct
// value for Type and PropertyID
value.ulPropTag = PROP_TAG(PT_LONG, PROP_ID(lpTags->aulPropTag[0]));
value.Value.l = (LONG)color;
if( HrSetOneProp(pMAPIProp, &value) != S_OK)
throw gcnew Exception(String::Format (
"Error setting AppointmentLabelColor: {0}.",color) );
// save the changes to the Item
pMessage->SaveChanges (KEEP_OPEN_READWRITE);
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
The method to retrieve the appointment label color is implemented as you can see here: // A well Google documented ID used to access an appointment color label
GUID magicId =
{
0x00062002, 0x0000, 0x0000,
{
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
}
};
LONG propertyName = 0x8214;
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID), (LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to acces the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName - we have an ID as Property here
pNamedProp->ulKind = MNID_ID;
pNamedProp->Kind.lID = propertyName;
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames(1, &pNamedProp, STGM_READ, &lpTags ) != S_OK)
throw gcnew Exception(String::Format (
"Error retrieving GetIDsFromNames: {0}.",propertyName) );
// The PROP_TAG macro creates the correct value for Type and PropertyID
ULONG propTag = PROP_TAG(PT_LONG, PROP_ID(lpTags->aulPropTag[0]));
// use the HrGetOneProp method to retrieve the profile name property
// the lpPropValue pointer points to the result value
if (HrGetOneProp(pMAPIProp,propTag,&lpSPropValue) != S_OK)
throw gcnew Exception("HrGetOneProp failed for named property !");
if (( lpSPropValue->Value.l > 0) && ( lpSPropValue->Value.l < 11))
return (Fabric::AppointmentLabelColor ) lpSPropValue->Value.l;
else
return Fabric::AppointmentLabelColor::Default;
NotesWhen you deploy this DLL along with your application:
Helmut Obertanner - June 2007 X4U electronix History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||