Mapi.cs
author Thomas
Mon, 28 Oct 2019 08:15:41 +0100
branchOUT-619
changeset 2834 4fa3adadc41d
parent 2597 a745e942a666
permissions -rw-r--r--
Adjust image size
     1 ´╗┐using System;
     2 using System.Collections.Generic;
     3 using System.Runtime.InteropServices;
     4 using Outlook = Microsoft.Office.Interop.Outlook;
     5 
     6 namespace pEp
     7 {
     8     /// <summary>
     9     /// Provides methods to interact with MAPI via native (p/invoke) calls.
    10     /// </summary>
    11     internal class Mapi
    12     {
    13         #region Definitions
    14         /// <summary>
    15         /// A collection of MAPI error or warning codes.
    16         /// </summary>
    17         internal enum HResult : uint
    18         {
    19             S_FALSE                             = 0x00000001,
    20             S_OK                                = 0x00000000,
    21 
    22             E_NOTIMPL                           = 0x80004001,
    23 
    24             MAPI_E_INTERFACE_NOT_SUPPORTED	    = 0x80004002,
    25             MAPI_E_INTERFACE_NOT_SUPPORTED_2    = 0x80000004,
    26             MAPI_E_CALL_FAILED                  = 0x80004005,
    27             MAPI_E_NOT_ENOUGH_MEMORY            = 0x8007000E,
    28             MAPI_E_INVALID_PARAMETER            = 0x80000003,
    29             MAPI_E_NO_ACCESS                    = 0x80070005,
    30             MAPI_E_NO_ACCESS_2                  = 0x80000009,
    31 
    32             E_INVALIDARG                        = 0x80070057,
    33             E_OUTOFMEMORY                       = 0x80000002,
    34             E_UNEXPECTED                        = 0x8000FFFF,
    35             E_FAIL                              = 0x80000008,
    36 
    37             MAPI_E_NO_SUPPORT                   = 0x80040000 | 0x102,
    38             MAPI_E_BAD_CHARWIDTH                = 0x80040000 | 0x103,
    39             MAPI_E_STRING_TOO_LONG              = 0x80040000 | 0x105,
    40             MAPI_E_UNKNOWN_FLAGS                = 0x80040000 | 0x106,
    41             MAPI_E_INVALID_ENTRYID              = 0x80040000 | 0x107,
    42             MAPI_E_INVALID_OBJECT               = 0x80040000 | 0x108,
    43             MAPI_E_OBJECT_CHANGED               = 0x80040000 | 0x109,
    44             MAPI_E_OBJECT_DELETED               = 0x80040000 | 0x10A,
    45             MAPI_E_BUSY                         = 0x80040000 | 0x10B,
    46             MAPI_E_NOT_ENOUGH_DISK              = 0x80040000 | 0x10D,
    47             MAPI_E_NOT_ENOUGH_RESOURCES         = 0x80040000 | 0x10E,
    48             MAPI_E_NOT_FOUND                    = 0x80040000 | 0x10F,
    49             MAPI_E_VERSION                      = 0x80040000 | 0x110,
    50             MAPI_E_LOGON_FAILED                 = 0x80040000 | 0x111,
    51             MAPI_E_SESSION_LIMIT                = 0x80040000 | 0x112,
    52             MAPI_E_USER_CANCEL                  = 0x80040000 | 0x113,
    53             MAPI_E_UNABLE_TO_ABORT              = 0x80040000 | 0x114,
    54             MAPI_E_NETWORK_ERROR                = 0x80040000 | 0x115,
    55             MAPI_E_DISK_ERROR                   = 0x80040000 | 0x116,
    56             MAPI_E_TOO_COMPLEX                  = 0x80040000 | 0x117,
    57             MAPI_E_BAD_COLUMN                   = 0x80040000 | 0x118,
    58             MAPI_E_EXTENDED_ERROR               = 0x80040000 | 0x119,
    59             MAPI_E_COMPUTED                     = 0x80040000 | 0x11A,
    60             MAPI_E_CORRUPT_DATA                 = 0x80040000 | 0x11B,
    61             MAPI_E_UNCONFIGURED                 = 0x80040000 | 0x11C,
    62             MAPI_E_FAILONEPROVIDER              = 0x80040000 | 0x11D,
    63             MAPI_E_UNKNOWN_CPID                 = 0x80040000 | 0x11E,
    64             MAPI_E_UNKNOWN_LCID                 = 0x80040000 | 0x11F,
    65 
    66             MAPI_E_CORRUPT_STORE                = 0x80040000 | 0x600,
    67             MAPI_E_NOT_IN_QUEUE                 = 0x80040000 | 0x601,
    68             MAPI_E_NO_SUPPRESS                  = 0x80040000 | 0x602,
    69             MAPI_E_COLLISION                    = 0x80040000 | 0x604,
    70             MAPI_E_NOT_INITIALIZED              = 0x80040000 | 0x605,
    71             MAPI_E_NON_STANDARD                 = 0x80040000 | 0x606,
    72             MAPI_E_NO_RECIPIENTS                = 0x80040000 | 0x607,
    73             MAPI_E_SUBMITTED                    = 0x80040000 | 0x608,
    74             MAPI_E_HAS_FOLDERS                  = 0x80040000 | 0x609,
    75             MAPI_E_HAS_MESSAGES                 = 0x80040000 | 0x60A,
    76             MAPI_E_FOLDER_CYCLE                 = 0x80040000 | 0x60B
    77         }
    78 
    79         /// <summary>
    80         /// Interface IDs used to retrieve the specific MAPI Interfaces from the IUnknown object.
    81         /// </summary>
    82         internal static class MAPIInterfaceIds
    83         {
    84             public const string IMAPISession        = "00020300-0000-0000-C000-000000000046";
    85             public const string IMAPIProp           = "00020303-0000-0000-C000-000000000046";
    86             public const string IMAPITable          = "00020301-0000-0000-C000-000000000046";
    87             public const string IMAPIMsgStore       = "00020306-0000-0000-C000-000000000046";
    88             public const string IMAPIFolder         = "0002030C-0000-0000-C000-000000000046";
    89             public const string IMAPISpoolerService = "0002031E-0000-0000-C000-000000000046";
    90             public const string IMAPIStatus         = "0002031E-0000-0000-C000-000000000046";
    91             public const string IMessage            = "00020307-0000-0000-C000-000000000046";
    92             public const string IAddrBook           = "00020309-0000-0000-C000-000000000046";
    93             public const string IProfSect           = "00020304-0000-0000-C000-000000000046";
    94             public const string IMAPIContainer      = "0002030B-0000-0000-C000-000000000046";
    95             public const string IABContainer        = "0002030D-0000-0000-C000-000000000046";
    96             public const string IMsgServiceAdmin    = "0002031D-0000-0000-C000-000000000046";
    97             public const string IProfAdmin          = "0002031C-0000-0000-C000-000000000046";
    98             public const string IMailUser           = "0002030A-0000-0000-C000-000000000046";
    99             public const string IDistList           = "0002030E-0000-0000-C000-000000000046";
   100             public const string IAttachment         = "00020308-0000-0000-C000-000000000046";
   101             public const string IMAPIControl        = "0002031B-0000-0000-C000-000000000046";
   102             public const string IMAPILogonRemote    = "00020346-0000-0000-C000-000000000046";
   103             public const string IMAPIForm           = "00020327-0000-0000-C000-000000000046";
   104 
   105             public const string IID_IStorage        = "0000000B-0000-0000-C000-000000000046";
   106             public const string IID_IStream         = "0000000C-0000-0000-C000-000000000046";
   107         }
   108 
   109         /// <summary>
   110         /// Save options for the IMAPIProp.SaveChanges method.
   111         /// </summary>
   112         internal enum SaveOption
   113         {
   114             KEEP_OPEN_READONLY = 0x00000001,
   115             KEEP_OPEN_READWRITE = 0x00000002,
   116             FORCE_SAVE = 0x00000004
   117         }
   118 
   119         /// <summary>
   120         /// Deletion options for the IMAPIFolder.DeleteMessages method.
   121         /// </summary>
   122         internal enum DeletionFlags : uint
   123         {
   124             DELETE_HARD_DELETE = 0x00000010,
   125             //MESSAGE_DIALOG = ?
   126         }
   127 
   128         /// <summary>
   129         /// Flag to indicate that IMAPIProps::GetProps should return unicode values.
   130         /// </summary>
   131         private const uint MAPI_UNICODE = 0x80000000;
   132 
   133         #endregion
   134 
   135         #region Interfaces
   136 
   137         /// <summary>
   138         /// Manages high-level operations on container objects such as address books, distribution lists, and folders.
   139         /// </summary>
   140         [
   141             ComImport,
   142             ComVisible(false),
   143             InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
   144             Guid(Mapi.MAPIInterfaceIds.IMAPIContainer)
   145         ]
   146         internal interface IMAPIContainer : IMAPIProp
   147         {
   148             [return: MarshalAs(UnmanagedType.I4)]
   149             [PreserveSig]
   150             int GetContentsTable();
   151             [return: MarshalAs(UnmanagedType.I4)]
   152             [PreserveSig]
   153             int GetHierarchyTable();
   154             [return: MarshalAs(UnmanagedType.I4)]
   155             [PreserveSig]
   156             int OpenEntry(uint cbEntryId, IntPtr entryId, ref Guid iid, uint flags, out IntPtr type, out IntPtr iUnk);
   157             [return: MarshalAs(UnmanagedType.I4)]
   158             [PreserveSig]
   159             int SetSearchCriteria();
   160             [return: MarshalAs(UnmanagedType.I4)]
   161             [PreserveSig]
   162             int GetSearchCriteria();
   163         }
   164 
   165         /// <summary>
   166         /// Performs operations on the messages and subfolders in a folder.
   167         /// </summary>
   168         [
   169             ComImport,
   170             ComVisible(false),
   171             InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
   172             Guid(Mapi.MAPIInterfaceIds.IMAPIFolder)
   173         ]
   174         internal interface IMAPIFolder : IMAPIContainer
   175         {
   176             /// <summary>
   177             /// Creates a new message.
   178             /// <paramref name="_interface"/>A pointer to the interface identifier (IID) that represents the interface 
   179             /// to be used to access the new message. Valid interface identifiers include IID_IUnknown, IID_IMAPIProp, 
   180             /// IID_IMAPIContainer, and IID_IMAPIFolder. Passing NULL causes the message store provider to return the 
   181             /// standard message interface, IMessage::IMAPIProp.</param>
   182             /// <param name="flags">A bitmask of flags that controls how the message is created.</param>
   183             /// <param name="message">A pointer to a pointer to the newly created message.</param>
   184             /// See: https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/imapifolder-createmessage
   185             /// </summary>
   186             /// <returns>The status of this method.</returns>
   187             [return: MarshalAs(UnmanagedType.I4)]
   188             [PreserveSig]
   189             int CreateMessage(ref Guid lpiid, uint flags, [MarshalAs(UnmanagedType.Interface)] out IMessage message);
   190 
   191             /// <summary>
   192             /// Copies or moves one or more messages. 
   193             /// </summary>
   194             /// <returns>The status of this method.</returns>
   195             int CopyMessages();
   196 
   197             /// <summary>
   198             /// Deletes one or more messages. 
   199             /// </summary>
   200             /// <param name="msgList">A pointer to an ENTRYLIST structure that contains the 
   201             /// number of messages to delete and an array of ENTRYID structures that identify the messages.</param>
   202             /// <param name="uiParam">A handle to the parent window of the progress indicator. The ulUIParam 
   203             /// parameter is ignored unless the MESSAGE_DIALOG flag is set in the ulFlags parameter.</param>
   204             /// <param name="progress">A pointer to a progress object that displays a progress indicator. 
   205             /// If IntPtr.Zero is passed in lpProgress, the message store provider displays a progress indicator 
   206             /// by using the MAPI progress object implementation. The lpProgress parameter is ignored unless the 
   207             /// MESSAGE_DIALOG flag is set in the ulFlags parameter.</param>
   208             /// <param name="flags">A bitmask of flags that controls how the messages are deleted. The following flags can be set:
   209             /// DELETE_HARD_DELETE: Permanently removes all messages, including soft-deleted ones.
   210             /// MESSAGE_DIALOG: Displays a progress indicator as the operation proceeds.</param>
   211             /// See: https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/imapifolder-deletemessages
   212             /// <returns>The status of this method.</returns>
   213             [return: MarshalAs(UnmanagedType.I4)]
   214             [PreserveSig]
   215             int DeleteMessages(IntPtr msgList, uint uiParam, IntPtr progress, uint flags);
   216 
   217             /// <summary>
   218             /// Creates a new subfolder. 
   219             /// </summary>
   220             /// <returns>The status of this method.</returns>
   221             int CreateFolder();
   222 
   223             /// <summary>
   224             /// Copies or moves a subfolder. 
   225             /// </summary>
   226             /// <returns>The status of this method.</returns>
   227             int CopyFolder();
   228 
   229             /// <summary>
   230             /// Deletes a subfolder. 
   231             /// </summary>
   232             /// <returns>The status of this method.</returns>
   233             int DeleteFolder();
   234 
   235             /// <summary>
   236             /// Sets or clears the MSGFLAG_READ flag in the PR_MESSAGE_FLAGS (PidTagMessageFlags) property 
   237             /// of one or more of the folder's messages, and manages the sending of read reports.
   238             /// </summary>
   239             /// <returns>The status of this method.</returns>
   240             int SetReadFlags();
   241 
   242             /// <summary>
   243             /// Obtains the status associated with a message in a particular folder.
   244             /// </summary>
   245             /// <returns>The status of this method.</returns>
   246             int GetMessageStatus();
   247 
   248             /// <summary>
   249             ///  Sets the status associated with a message.
   250             /// </summary>
   251             /// <returns>The status of this method.</returns>
   252             int SetMessageStatus();
   253 
   254             /// <summary>
   255             /// Sets the default sort order for a folder's contents table.
   256             /// </summary>
   257             /// <returns>The status of this method.</returns>
   258             int SaveContentsSort();
   259 
   260             /// <summary>
   261             ///  Deletes all messages and subfolders from a folder without deleting the folder itself. 
   262             /// </summary>
   263             /// <returns>The status of this method.</returns>
   264             int EmptyFolder();
   265         }
   266 
   267 
   268         /// <summary>
   269         /// Enables clients, service providers, and MAPI to work with properties. All objects that 
   270         /// support properties implement this interface.
   271         /// </summary>
   272         [
   273             ComImport,
   274             ComVisible(false),
   275             InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
   276             Guid(Mapi.MAPIInterfaceIds.IMAPIProp)
   277         ]
   278         internal interface IMAPIProp
   279         {
   280             [return: MarshalAs(UnmanagedType.I4)]
   281             [PreserveSig]
   282             int GetLastError();
   283             [return: MarshalAs(UnmanagedType.I4)]
   284             [PreserveSig]
   285             int SaveChanges(uint uFlags);
   286             [return: MarshalAs(UnmanagedType.I4)]
   287             [PreserveSig]
   288             int GetProps([MarshalAs(UnmanagedType.LPArray)] uint[] lpPropTagArray, uint flags, out uint count, out IntPtr propArray);
   289             [return: MarshalAs(UnmanagedType.I4)]
   290             [PreserveSig]
   291             int GetPropList();
   292             [return: MarshalAs(UnmanagedType.I4)]
   293             [PreserveSig]
   294             int OpenProperty(uint ulPropTag, ref Guid lpiid, uint ulInterfaceOptions, uint ulFlags, out IntPtr lppUnk);
   295             [return: MarshalAs(UnmanagedType.I4)]
   296             [PreserveSig]
   297             int SetProps(uint values, IntPtr propArray, IntPtr problems);
   298             [return: MarshalAs(UnmanagedType.I4)]
   299             [PreserveSig]
   300             int DeleteProps();
   301             [return: MarshalAs(UnmanagedType.I4)]
   302             [PreserveSig]
   303             int CopyTo();
   304             [return: MarshalAs(UnmanagedType.I4)]
   305             [PreserveSig]
   306             int CopyProps();
   307             [return: MarshalAs(UnmanagedType.I4)]
   308             [PreserveSig]
   309             int GetNamesFromIDs();
   310             [return: MarshalAs(UnmanagedType.I4)]
   311             [PreserveSig]
   312             int GetIDsFromNames();
   313         }
   314 
   315         /// <summary>
   316         /// Provides a read-only view of a table. IMAPITable is used by clients and service providers to manipulate the way a table appears. 
   317         /// </summary>
   318         [Guid("00020301-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   319         public interface IMAPITable
   320         {
   321             /// <summary>
   322             /// Returns a MAPIERROR structure containing information about the previous error on the table.
   323             /// </summary>
   324             /// <param name="hResult">HRESULT containing the error generated in the previous method call.</param>
   325             /// <param name="ulFlags">Bitmask of flags that controls the type of the returned strings. </param>
   326             /// <param name="lppMAPIError">Pointer to a pointer to the returned MAPIERROR structure containing version, component, and context information for the error.</param>
   327             /// <returns>S_OK, if the call succeeded and has returned the expected value or values; otherwise, failed.</returns>
   328             int GetLastError(int hResult, uint ulFlags, out IntPtr lppMAPIError);
   329             /// <summary>
   330             /// Registers an advise sink object to receive notification of specified events affecting the table.
   331             /// </summary>
   332             /// <param name="ulEventMask">Value indicating the type of event that will generate the notification.</param>
   333             /// <param name="lpAdviseSink">Pointer to an advise sink object to receive the subsequent notifications. This advise sink object must have been already allocated.</param>
   334             /// <param name="lpulConnection">Pointer to a nonzero value that represents the successful notification registration.</param>
   335             /// <returns>S_OK, if the notification registration successfully completed; otherwise, failed.</returns>
   336             int Advise(uint ulEventMask, IntPtr lpAdviseSink, IntPtr lpulConnection);
   337             /// <summary>
   338             /// Cancels the sending of notifications previously set up with a call to the IMAPITable::Advise method.
   339             /// </summary>
   340             /// <param name="ulConnection">The number of the registration connection returned by a call to IMAPITable::Advise.</param>
   341             /// <returns>S_OK, if the call succeeded; otherwise, failed.</returns>
   342             int Unadvise(uint ulConnection);
   343             /// <summary>
   344             /// Returns the table's status and type.
   345             /// </summary>
   346             /// <param name="lpulTableStatus">Pointer to a value indicating the status of the table.</param>
   347             /// <param name="lpulTableType">Pointer to a value that indicates the table's type.</param>
   348             /// <returns>S_OK, if the table's status was successfully returned; otherwise, failed.</returns>
   349             int GetStatus(IntPtr lpulTableStatus, IntPtr lpulTableType);
   350             /// <summary>
   351             /// Defines the particular properties and order of properties to appear as columns in the table.
   352             /// </summary>
   353             /// <param name="lpPropTagArray">Pointer to an array of property tags identifying properties to be included as columns in the table. </param>
   354             /// <param name="ulFlags">Bitmask of flags that controls the return of an asynchronous call to SetColumns.</param>
   355             /// <returns>S_OK, if the column setting operation was successful; otherwise, failed.</returns>
   356             int SetColumns([MarshalAs(UnmanagedType.LPArray)] uint[] lpPropTagArray, uint ulFlags);
   357             /// <summary>
   358             /// Returns a list of columns for the table.
   359             /// </summary>
   360             /// <param name="ulFlags">Bitmask of flags that indicates which column set should be returned.</param>
   361             /// <param name="lpPropTagArray">Pointer to an SPropTagArray structure containing the property tags for the column set.</param>
   362             /// <returns>S_OK, if the column set was successfully returned; otherwise, failed.</returns>
   363             int QueryColumns(uint ulFlags, IntPtr lpPropTagArray);
   364             /// <summary>
   365             /// Returns the total number of rows in the table. 
   366             /// </summary>
   367             /// <param name="ulFlags">Reserved; must be zero.</param>
   368             /// <param name="lpulCount">Pointer to the number of rows in the table.</param>
   369             /// <returns>S_OK, if the row count was successfully returned; otherwise, failed.</returns>
   370             int GetRowCount(uint ulFlags, out uint lpulCount);
   371             /// <summary>
   372             /// Moves the cursor to a specific position in the table.
   373             /// </summary>
   374             /// <param name="bkOrigin">The bookmark identifying the starting position for the seek operation.</param>
   375             /// <param name="lRowCount">The signed count of the number of rows to move, starting from the bookmark identified by the bkOrigin parameter.</param>
   376             /// <param name="lplRowsSought">If lRowCount is a valid pointer on input, lplRowsSought points to the number of rows that were processed in the seek operation, the sign of which indicates the direction of search, forward or backward. If lRowCount is negative, then lplRowsSought is negative.</param>
   377             /// <returns>S_OK, if the seek operation was successful; otherwise, failed.</returns>
   378             int SeekRow(int bkOrigin, int lRowCount, out IntPtr lplRowsSought);
   379             /// <summary>
   380             /// Moves the cursor to an approximate fractional position in the table. 
   381             /// </summary>
   382             /// <param name="ulNumerator">The numerator of the fraction representing the table position</param>
   383             /// <param name="ulDenominator">The denominator of the fraction representing the table position</param>
   384             /// <returns>S_OK, if the seek operation was successful; otherwise, failed.</returns>
   385             int SeekRowApprox(uint ulNumerator, uint ulDenominator);
   386             /// <summary>
   387             /// Retrieves the current table row position of the cursor, based on a fractional value.
   388             /// </summary>
   389             /// <param name="lpulRow">Pointer to the number of the current row.</param>
   390             /// <param name="lpulNumerator">Pointer to the numerator for the fraction identifying the table position.</param>
   391             /// <param name="lpulDenominator">Pointer to the denominator for the fraction identifying the table position.</param>
   392             /// <returns>S_OK, if the method returned valid values in lpulRow, lpulNumerator, and lpulDenominator; otherwise, failed.</returns>
   393             int QueryPosition(IntPtr lpulRow, IntPtr lpulNumerator, IntPtr lpulDenominator);
   394             /// <summary>
   395             /// Finds the next row in a table that matches specific search criteria and moves the cursor to that row.
   396             /// </summary>
   397             /// <param name="lpRestriction">A pointer to an SRestriction structure that describes the search criteria.</param>
   398             /// <param name="BkOrigin">A bookmark identifying the row where FindRow should begin its search.</param>
   399             /// <param name="ulFlags">A bitmask of flags that controls the direction of the search.</param>
   400             /// <returns>S_OK, if the find operation was successful; otherwise, failed.</returns>
   401             int FindRow(out IntPtr lpRestriction, uint BkOrigin, uint ulFlags);
   402             /// <summary>
   403             /// Applies a filter to a table, reducing the row set to only those rows matching the specified criteria.
   404             /// </summary>
   405             /// <param name="lpRestriction">Pointer to an SRestriction structure defining the conditions of the filter. Passing NULL in the lpRestriction parameter removes the current filter.</param>
   406             /// <param name="ulFlags">Bitmask of flags that controls the timing of the restriction operation.</param>
   407             /// <returns>S_OK, if the filter was successfully applied; otherwise, failed.</returns>
   408             int Restrict(out IntPtr lpRestriction, uint ulFlags);
   409             /// <summary>
   410             /// Creates a bookmark at the table's current position.
   411             /// </summary>
   412             /// <param name="lpbkPosition">Pointer to the returned 32-bit bookmark value. This bookmark can later be passed in a call to the IMAPITable::SeekRow method</param>
   413             /// <returns>S_OK, if the call succeeded and has returned the expected value or values; otherwise, failed.</returns>
   414             int CreateBookmark(IntPtr lpbkPosition);
   415             /// <summary>
   416             /// Releases the memory associated with a bookmark.
   417             /// </summary>
   418             /// <param name="bkPosition">The bookmark to be freed, created by calling the IMAPITable::CreateBookmark method.</param>
   419             /// <returns>S_OK, if the bookmark was successfully freed; otherwise, failed.</returns>
   420             int FreeBookmark(IntPtr bkPosition);
   421             /// <summary>
   422             /// Orders the rows of the table, depending on sort criteria.
   423             /// </summary>
   424             /// <param name="lpSortCriteria">Pointer to an SSortOrderSet structure that contains the sort criteria to apply.</param>
   425             /// <param name="ulFlags">Bitmask of flags that controls the timing of the IMAPITable::SortTable operation.</param>
   426             /// <returns>S_OK, if the sort operation was successful; otherwise, failed.</returns>
   427             int SortTable(IntPtr lpSortCriteria, int ulFlags);
   428             /// <summary>
   429             /// Retrieves the current sort order for a table.
   430             /// </summary>
   431             /// <param name="lppSortCriteria">Pointer to a pointer to the SSortOrderSet structure holding the current sort order.</param>
   432             /// <returns>S_OK, if the current sort order was successfully returned; otherwise, failed.</returns>
   433             int QuerySortOrder(IntPtr lppSortCriteria);
   434             /// <summary>
   435             /// Returns one or more rows from a table, beginning at the current cursor position.
   436             /// </summary>
   437             /// <param name="lRowCount">Maximum number of rows to be returned.</param>
   438             /// <param name="ulFlags">Bitmask of flags that control how rows are returned.</param>
   439             /// <param name="lppRows">Pointer to a pointer to an SRowSet structure holding the table rows.</param>
   440             /// <returns>S_OK, if the rows were successfully returned; otherwise, failed.</returns>
   441             int QueryRows(int lRowCount, uint ulFlags, out IntPtr lppRows);
   442             /// <summary>
   443             /// Stops any asynchronous operations currently in progress for the table.
   444             /// </summary>
   445             /// <returns>S_OK, if one or more asynchronous operations have been stopped; otherwise, failed.</returns>
   446             int Abort();
   447             /// <summary>
   448             /// Expands a collapsed table category, adding the leaf or lower-level heading rows belonging to the category to the table view.
   449             /// </summary>
   450             /// <param name="cbInstanceKey">The count of bytes in the PR_INSTANCE_KEY property pointed to by the pbInstanceKey parameter.</param>
   451             /// <param name="pbInstanceKey">A pointer to the PR_INSTANCE_KEY property that identifies the heading row for the category.</param>
   452             /// <param name="ulRowCount">The maximum number of rows to return in the lppRows parameter. </param>
   453             /// <param name="ulFlags">Reserved; must be zero.</param>
   454             /// <param name="lppRows">A pointer to an SRowSet structure receiving the first (up to ulRowCount) rows that have been inserted into the table view as a result of the expansion.</param>
   455             /// <param name="lpulMoreRows">A pointer to the total number of rows that were added to the table view.</param>
   456             /// <returns>S_OK, if the category was expanded successfully; otherwise, failed.</returns>
   457             int ExpandRow(uint cbInstanceKey, IntPtr pbInstanceKey, uint ulRowCount, uint ulFlags, IntPtr lppRows, IntPtr lpulMoreRows);
   458             /// <summary>
   459             /// Collapses an expanded table category, removing any lower-level headings and leaf rows belonging to the category from the table view.
   460             /// </summary>
   461             /// <param name="cbInstanceKey">The count of bytes in the PR_INSTANCE_KEY property pointed to by the pbInstanceKey parameter.</param>
   462             /// <param name="pbInstanceKey">A pointer to the PR_INSTANCE_KEY property that identifies the heading row for the category. </param>
   463             /// <param name="ulFlags">Reserved; must be zero.</param>
   464             /// <param name="lpulRowCount">A pointer to the total number of rows that are being removed from the table view.</param>
   465             /// <returns>S_OK, if the collapse operation has succeeded; otherwise, failed.</returns>
   466             int CollapseRow(uint cbInstanceKey, IntPtr pbInstanceKey, uint ulFlags, IntPtr lpulRowCount);
   467             /// <summary>
   468             /// Suspends processing until one or more asynchronous operations in progress on the table have completed.
   469             /// </summary>
   470             /// <param name="ulFlags">Reserved; must be zero.</param>
   471             /// <param name="ulTimeout">Maximum number of milliseconds to wait for the asynchronous operation or operations to complete.</param>
   472             /// <param name="lpulTableStatus">On input, either a valid pointer or NULL. On output, if lpulTableStatus is a valid pointer, it points to the most recent status of the table. </param>
   473             /// <returns>S_OK, if the wait operation was successful; otherwise, failed.</returns>
   474             int WaitForCompletion(uint ulFlags, uint ulTimeout, IntPtr lpulTableStatus);
   475             /// <summary>
   476             /// Returns the data that is needed to rebuild the current collapsed or expanded state of a categorized table.
   477             /// </summary>
   478             /// <param name="ulFlags">Reserved; must be zero.</param>
   479             /// <param name="cbInstanceKey">The count of bytes in the instance key pointed to by the lpbInstanceKey parameter.</param>
   480             /// <param name="lpbInstanceKey">A pointer to the PR_INSTANCE_KEY property of the row at which the current collapsed or expanded state should be rebuilt. </param>
   481             /// <param name="lpcbCollapseState">A pointer to the count of structures pointed to by the lppbCollapseState parameter.</param>
   482             /// <param name="lppbCollapseState">A pointer to a pointer to structures that contain data that describes the current table view.</param>
   483             /// <returns>S_OK, if the state for the categorized table was successfully saved; otherwise, failed.</returns>
   484             int GetCollapseState(uint ulFlags, uint cbInstanceKey, IntPtr lpbInstanceKey, IntPtr lpcbCollapseState, IntPtr lppbCollapseState);
   485             /// <summary>
   486             /// Rebuilds the current expanded or collapsed state of a categorized table using data that was saved by a prior call to the IMAPITable::GetCollapseState method.
   487             /// </summary>
   488             /// <param name="ulFlags">Reserved; must be zero.</param>
   489             /// <param name="cbCollapseState">Count of bytes in the structure pointed to by the pbCollapseState parameter.</param>
   490             /// <param name="pbCollapseState">Pointer to the structures containing the data needed to rebuild the table view.</param>
   491             /// <param name="lpbkLocation">Pointer to a bookmark identifying the row in the table at which the collapsed or expanded state should be rebuilt. </param>
   492             /// <returns>S_OK, if the state of the categorized table was successfully rebuilt; otherwise, failed.</returns>
   493             int SetCollapseState(uint ulFlags, uint cbCollapseState, IntPtr pbCollapseState, IntPtr lpbkLocation);
   494         }
   495 
   496         /// <summary>
   497         /// The IMessage interface defines methods and properties used to manage messages.
   498         /// </summary>
   499         [ComImport()]
   500         [Guid(Mapi.MAPIInterfaceIds.IMessage)]        
   501         [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   502         public interface IMessage
   503         {
   504             int GetLastError(int hResult, uint ulFlags, out IntPtr lppMAPIError);
   505             int SaveChanges(uint ulFlags);
   506             int GetProps(IntPtr lpPropTagArray, uint ulFlags, out uint lpcValues, out IntPtr lppPropArray);
   507             int GetPropList(uint ulFlags, out IntPtr lppPropTagArray);
   508             [return: MarshalAs(UnmanagedType.I4)]
   509             [PreserveSig]
   510             int OpenProperty(uint ulPropTag, ref Guid lpiid, uint ulInterfaceOptions, uint ulFlags, out IntPtr lppUnk);
   511             int SetProps(uint cValues, IntPtr lpPropArray, out IntPtr lppProblems);
   512             int DeleteProps(IntPtr lpPropTagArray, out IntPtr lppProblems);
   513             int CopyTo(uint ciidExclude, ref Guid rgiidExclude, IntPtr lpExcludeProps, uint ulUIParam, IntPtr lpProgress, ref Guid lpInterface, IntPtr lpDestObj, uint ulFlags, out IntPtr lppProblems);
   514             int CopyProps(IntPtr lpIncludeProps, uint ulUIParam, IntPtr lpProgress, ref Guid lpInterface, IntPtr lpDestObj, uint ulFlags, out IntPtr lppProblems);
   515             int GetNamesFromIDs(out IntPtr lppPropTags, ref Guid lpPropSetGuid, uint ulFlags, out uint lpcPropNames, out IntPtr lpppPropNames);
   516             int GetIDsFromNames(uint cPropNames, ref IntPtr lppPropNames, uint ulFlags, out IntPtr lppPropTags);
   517             int GetAttachmentTable(uint ulFlags, out IMAPITable lppTable);
   518             int OpenAttach(uint ulAttachmentNum, ref Guid lpInterface, uint ulFlags, out IAttach lppAttach);
   519             int CreateAttach();
   520             int DeleteAttach();
   521             int GetRecipientTable(uint ulFlags, out IMAPITable lppTable);
   522             int ModifyRecipients();
   523             int SubmitMessage(uint ulFlags);
   524             int SetReadFlag(uint ulFlags);
   525         }
   526 
   527         [ComImport()]
   528         [Guid(Mapi.MAPIInterfaceIds.IAttachment)]
   529         [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   530         public interface IAttach : IMAPIProp
   531         { }
   532 
   533         #endregion
   534 
   535         #region Structures
   536 
   537         /// <summary>
   538         /// The SPropValue structure describes a MAPI property.
   539         /// </summary>
   540         public struct SPropValue
   541         {
   542             /// <summary>
   543             /// Property tag for the property. Property tags are 32-bit unsigned integers consisting of the property's unique identifier in the high-order 16 bits and the property's type in the low-order 16 bits.
   544             /// </summary>
   545             public uint PropTag;
   546 
   547             /// <summary>
   548             /// Reserved for MAPI; do not use.
   549             /// </summary>
   550 #pragma warning disable 649
   551             public uint DwAlignPad;
   552 #pragma warning restore 649
   553 
   554             /// <summary>
   555             /// Union of data values, the specific value dictated by the property type.
   556             /// </summary>
   557             public PV Value;
   558         }
   559 
   560         /// <summary>
   561         /// Union of data values for the SPropValue.value property.
   562         /// </summary>
   563         [StructLayout(LayoutKind.Explicit, Size = 8)]
   564         public struct PV
   565         {
   566             [FieldOffset(0)]
   567             public short i;
   568             [FieldOffset(0)]
   569             public int l;
   570             [FieldOffset(0)]
   571             public uint ul;
   572             [FieldOffset(0)]
   573             public float flt;
   574             [FieldOffset(0)]
   575             public double dbl;
   576             [FieldOffset(0)]
   577             public ushort b;
   578             [FieldOffset(0)]
   579             public double at;
   580             [FieldOffset(0)]
   581             public IntPtr lpszA;
   582             [FieldOffset(0)]
   583             public IntPtr lpszW;
   584             [FieldOffset(0)]
   585             public IntPtr lpguid;
   586             /*[FieldOffset(0)]
   587             public IntPtr bin;*/
   588             [FieldOffset(0)]
   589             public ulong li;
   590             [FieldOffset(0)]
   591             public SRowSet bin;
   592         }
   593 
   594         /// <summary>
   595         /// Describes a row from a table that contains selected properties for a specific object.
   596         /// </summary>
   597         [StructLayout(LayoutKind.Sequential)]
   598         public struct SRow
   599         {
   600             public uint ulAdrEntryPad;
   601             public uint cValues;
   602             public IntPtr lpProps;
   603         }
   604 
   605         /// <summary>
   606         /// Contains an array of SRow structures. Each SRow structure describes a row from a table.
   607         /// </summary>
   608         [StructLayout(LayoutKind.Sequential)]
   609         public struct SRowSet
   610         {
   611             public uint cRows;
   612             public IntPtr aRow; // pSRow
   613 
   614             public byte[] AsBytes
   615             {
   616                 get
   617                 {
   618                     byte[] b = new byte[this.cRows];
   619                     for (int i = 0; i < this.cRows; i++)
   620                         b[i] = Marshal.ReadByte(aRow, i);
   621                     return b;
   622                 }
   623             }
   624         }
   625 
   626         #endregion
   627 
   628         #region Methods
   629 
   630         /// <summary>
   631         /// Retrieves a managed object from the SPropValue structure that is returned by GetProps etc.
   632         /// </summary>
   633         /// <param name="mapiProperty">The MAPI property the value was retrieved for.</param>
   634         /// <param name="sPropValue">The value to convert.</param>
   635         /// <returns>The converted object or null if an error occured.</returns>
   636         public static object ConvertSPropValueToObject(MapiProperty.MapiProp mapiProperty, SPropValue sPropValue)
   637         {
   638             object propertyValue = null;
   639 
   640             try
   641             {
   642                 switch (mapiProperty.DataType)
   643                 {
   644                     case MapiProperty.MapiDataType.PtypBoolean:
   645                         {
   646                             propertyValue = Convert.ToBoolean(sPropValue.Value.b);
   647                         }
   648                         break;
   649                     case MapiProperty.MapiDataType.PtypString:
   650                         {
   651                             if ((((uint)sPropValue.Value.lpszW).ToString("X2") is string hexValue) &&
   652                                 (hexValue.StartsWith("800")))
   653                             {
   654                                 Log.Error("ConvertSPropValueToObject: Cannot convert MAPI property {0} to string: {1}", mapiProperty.DaslName, hexValue);
   655                             }
   656                             else
   657                             {
   658                                 propertyValue = Marshal.PtrToStringUni(sPropValue.Value.lpszW);
   659                             }
   660                         }
   661                         break;
   662                     case MapiProperty.MapiDataType.PtypString8:
   663                         {
   664                             if ((((uint)sPropValue.Value.lpszA).ToString("X2") is string hexValue) &&
   665                                 (hexValue.StartsWith("800")))
   666                             {
   667                                 Log.Error("ConvertSPropValueToObject: Cannot convert MAPI property {0} to string8: {1}", mapiProperty.DaslName, hexValue);
   668                             }
   669                             else
   670                             {
   671                                 propertyValue = Marshal.PtrToStringUni(sPropValue.Value.lpszA);
   672                             }
   673                         }
   674                         break;
   675                     case MapiProperty.MapiDataType.PtypInteger16:
   676                         {
   677                             propertyValue = sPropValue.Value.i;
   678                         }
   679                         break;
   680                     case MapiProperty.MapiDataType.PtypInteger32:
   681                         {
   682                             propertyValue = sPropValue.Value.l;
   683                         }
   684                         break;
   685                     case MapiProperty.MapiDataType.PtypFloating32:
   686                         {
   687                             propertyValue = sPropValue.Value.flt;
   688                         }
   689                         break;
   690                     case MapiProperty.MapiDataType.PtypFloating64:
   691                     case MapiProperty.MapiDataType.PtypFloatingTime:
   692                         {
   693                             propertyValue = sPropValue.Value.at;
   694                         }
   695                         break;
   696                     case MapiProperty.MapiDataType.PtypInteger64:
   697                         {
   698                             propertyValue = sPropValue.Value.li;
   699                         }
   700                         break;
   701                     case MapiProperty.MapiDataType.PtypGuid:
   702                         {
   703                             propertyValue = Marshal.PtrToStructure(sPropValue.Value.lpguid, typeof(Guid));
   704                         }
   705                         break;
   706                     case MapiProperty.MapiDataType.PtypBinary:
   707                         {
   708                             propertyValue = sPropValue.Value.bin;
   709                         }
   710                         break;
   711                     default:
   712                         {
   713                             propertyValue = null;
   714                             Log.Error("ConvertSPropValueToObject: Error converting SPropValue. Data type {0} not supported.", Enum.GetName(typeof(MapiProperty.MapiDataType), mapiProperty.DataType));
   715                         }
   716                         break;
   717                 }
   718             }
   719             catch (Exception ex)
   720             {
   721                 propertyValue = null;
   722                 Log.Error("ConvertSPropValueToObject: Error converting MAPI property {0}. Exception: {1}.", mapiProperty.DaslName, ex.ToString());
   723             }
   724 
   725             return propertyValue;
   726         }
   727 
   728         /// <summary>
   729         /// Gets an attachment's properties.
   730         /// </summary>
   731         /// <param name="omi">The Outlook mail item that contains the attachment.</param>
   732         /// <param name="index">The attachment's index. 0-based.</param>
   733         /// <param name="mapiProperties">The MAPI properties to get its values for.</param>
   734         /// <param name="propertyValues">The property values that have been returned.</param>
   735         /// <returns>The status (Mapi.HResult) of this method.</returns>
   736         public static int GetAttachmentProperties(Outlook.MailItem omi,
   737                                                   int index,
   738                                                   List<MapiProperty.MapiProp> mapiProperties,
   739                                                   out MAPIProperties propertyValues)
   740         {
   741             int result = (int)Mapi.HResult.S_FALSE;
   742             propertyValues = null;
   743 
   744             try
   745             {
   746                 // Get the IMessage interface
   747                 IMessage iMessage = (IMessage)omi?.MAPIOBJECT;
   748                 if (iMessage == null)
   749                 {
   750                     throw new Exception("Could not get IMessage interface.");
   751                 }
   752 
   753                 // Open the attachment
   754                 Guid guid = (typeof(Mapi.IAttach)).GUID;
   755                 result = iMessage.OpenAttach((uint)index, ref guid, 0, out IAttach attachment);
   756 
   757                 // If we can't open the attachment, throw error
   758                 if (result != 0)
   759                 {
   760                     throw new Exception("Error opening attachment. " + Mapi.GetHResultError(result));
   761                 }
   762 
   763                 // Create the SPropValue structure
   764                 Mapi.SPropValue sPropValue = new Mapi.SPropValue();
   765                 int propValueSize = Marshal.SizeOf(sPropValue);
   766 
   767                 // Allocate memory for the array of SPropValues to set
   768                 int propertiesCount = mapiProperties.Count;
   769                 IntPtr propTagArray = Marshal.AllocHGlobal(propValueSize * propertiesCount);
   770 
   771                 // Create the property values array
   772                 uint[] propertyTags = new uint[propertiesCount + 1];
   773                 propertyTags[0] = (uint)propertiesCount;
   774                 for (int i = 0; i < propertiesCount; i++)
   775                 {
   776                     propertyTags[i + 1] = (uint)mapiProperties[i].Tag;
   777                 }
   778 
   779                 // Get properties
   780                 result = attachment.GetProps(propertyTags, Mapi.MAPI_UNICODE, out uint valuesCount, out IntPtr propArray);
   781 
   782                 // If an error occured, just log at this point.
   783                 if (result != 0)
   784                 {
   785                     Log.Error("OpenAttachment: Error getting attachment properties. " + Mapi.GetHResultError(result));
   786                 }
   787 
   788                 // Convert the retrieved values
   789                 object[] values = new object[valuesCount];
   790                 for (int i = 0; i < valuesCount; i++)
   791                 {
   792                     sPropValue = (SPropValue)Marshal.PtrToStructure((propArray + (i * propValueSize)), typeof(SPropValue));
   793                     values[i] = Mapi.ConvertSPropValueToObject(mapiProperties[i], sPropValue);
   794                 }
   795 
   796                 // Check if returned values match properties count
   797                 if (propertiesCount != valuesCount)
   798                 {
   799                     throw new Exception("Properties count doesn't match values count.");
   800                 }
   801 
   802                 // Create return dictionary
   803                 propertyValues = new MAPIProperties();
   804                 for (int i = 0; i < valuesCount; i++)
   805                 {
   806                     propertyValues.Add(mapiProperties[i], values[i]);
   807                 }
   808             }
   809             catch (Exception ex)
   810             {
   811                 Log.Error("OpenAttachment: Error getting attachment.  " + ex.ToString());
   812             }
   813 
   814             return result;
   815         }
   816 
   817         /// <summary>
   818         /// Gets the attachment table of an Outlook mail item.
   819         /// </summary>
   820         /// <param name="omi">The Outlook mail item to get its attachment table for.</param>
   821         /// <param name="attachmentTable">The attachment table.</param>
   822         /// <returns>The status of this method.</returns>
   823         public static int GetAttachmentTable(Outlook.MailItem omi, out IMAPITable attachmentTable)
   824         {
   825             int result = (int)Mapi.HResult.S_FALSE;
   826             attachmentTable = null;
   827 
   828             // Get MAPI object from mail item
   829             object mapiObject = omi?.MAPIOBJECT;
   830             if (mapiObject == null)
   831             {
   832                 Log.Error("GetMAPIProperties: MAPI object is null. Property could not be set.");
   833                 return result;
   834             }
   835 
   836             // Pointer to IUnknown interface
   837             IntPtr IUnknown = IntPtr.Zero;
   838 
   839             try
   840             {
   841                 // Initialize MAPI
   842                 NativeMethods.MAPIInitialize(IntPtr.Zero);
   843 
   844                 // Get the IUnknown interface from the MAPI object
   845                 IUnknown = Marshal.GetIUnknownForObject(mapiObject);
   846 
   847                 // Get the attachment table
   848                 IMessage message = (IMessage)mapiObject;
   849                 result = message.GetAttachmentTable(0, out attachmentTable);
   850             }
   851             catch (Exception ex)
   852             {
   853                 Log.Error("OpenMAPIProperty: Error occured. " + ex.ToString());
   854             }
   855             finally
   856             {
   857                 if (IUnknown != IntPtr.Zero)
   858                 {
   859                     Marshal.Release(IUnknown);
   860                 }
   861 
   862                 NativeMethods.MAPIUninitialize();
   863             }
   864 
   865             return result;
   866         }
   867 
   868         /// <summary>
   869         /// Converts an error code into a meaningful error message (if available).
   870         /// </summary>
   871         /// <param name="error">The error code to convert.</param>
   872         /// <returns>A string with a meaningful error code or null if an error occurs.</returns>
   873         public static string GetHResultError(int error)
   874         {
   875             try
   876             {
   877                 foreach (var hResult in Enum.GetValues(typeof(Mapi.HResult)))
   878                 {
   879                     if (((int)(uint)hResult) == error)
   880                     {
   881                         return Enum.GetName(typeof(Mapi.HResult), hResult);
   882                     }
   883                 }
   884 
   885                 // As backup, return a hex value string
   886                 return error.ToString("X2");
   887             }
   888             catch (Exception ex)
   889             {
   890                 Log.Error("GetHResultError: Error getting HResult error. " + ex.ToString());
   891             }
   892 
   893             return null;
   894         }
   895 
   896         /// <summary>
   897         /// Gets a MAPI property value from an Outlook mail item.
   898         /// </summary>
   899         /// <param name="omi">The Outlook mail item to get the property value for.</param>
   900         /// <param name="mapiProperty">The MAPI property to get its value.</param>
   901         /// <returns>The value or null if it doesn't exist or an error occured.</returns>
   902         public static object GetMAPIProperty(Outlook.MailItem omi, MapiProperty.MapiProp mapiProperty)
   903         {
   904             return Mapi.GetMAPIProperty(omi?.MAPIOBJECT, Mapi.MAPIInterfaceIds.IMessage, mapiProperty);
   905         }
   906 
   907         /// <summary>
   908         /// Gets a MAPI property value from an Outlook folder item.
   909         /// </summary>
   910         /// <param name="omi">The Outlook folder item to get the property value for.</param>
   911         /// <param name="mapiProperty">The MAPI property to get its value.</param>
   912         /// <returns>The value or null if it doesn't exist or an error occured.</returns>
   913         public static object GetMAPIProperty(Outlook.Folder folder, MapiProperty.MapiProp mapiProperty)
   914         {
   915             return Mapi.GetMAPIProperty(folder?.MAPIOBJECT, Mapi.MAPIInterfaceIds.IMAPIFolder, mapiProperty);
   916         }
   917 
   918         /// <summary>
   919         /// Gets the specified MAPI property value for this store.
   920         /// </summary>
   921         /// <param name="store">The Outlook store to process with.</param>
   922         /// <param name="mapiProperty">The MAPI property to get its value for.</param>
   923         /// <returns>The property value or null if not found or an error occured.</returns>
   924         public static object GetMAPIProperty(Outlook.Store store, MapiProperty.MapiProp mapiProperty)
   925         {
   926             return Mapi.GetMAPIProperty(store?.MAPIOBJECT, Mapi.MAPIInterfaceIds.IMAPIMsgStore, mapiProperty);
   927         }
   928 
   929         /// <summary>
   930         /// Gets a MAPI property value from an Outlook item.
   931         /// </summary>
   932         /// <param name="mapiObject">The MAPIOBJECT property of the Outlook item to get the property value for.</param>
   933         /// <param name="mapiInterfaceId">The MAPI interface id. Has to be one of the ones defined in Mapi.MAPIInterfaceIds and
   934         /// has to match the type of the mapiObject parameter.</param>
   935         /// <param name="mapiProperty">The MAPI property to get its value.</param>
   936         /// <returns>The value or null if it doesn't exist or an error occured.</returns>
   937         private static object GetMAPIProperty(object mapiObject, string mapiInterfaceId, MapiProperty.MapiProp mapiProperty)
   938         {
   939             // Check passed parameters
   940             if ((mapiObject == null) ||
   941                 (string.IsNullOrEmpty(mapiInterfaceId)))
   942             {
   943                 Log.Error("GetMAPIProperty: MAPI object oder interface id is null. Returning null.");
   944                 return null;
   945             }
   946 
   947             object propertyValue = null;
   948 
   949             // Pointer to IUnknown interface
   950             IntPtr IUnknown = IntPtr.Zero;
   951 
   952             // Pointer to IMessage, IMAPIFolder etc. interface
   953             IntPtr IMAPIObject = IntPtr.Zero;
   954 
   955             // Pointer to IMAPIProp interface
   956             IntPtr IMAPIProp = IntPtr.Zero;
   957 
   958             // Structure that will hold the property value
   959             Mapi.SPropValue sPropValue;
   960 
   961             // A pointer that points to the SPropValue structure 
   962             IntPtr ptrPropValue = IntPtr.Zero;
   963 
   964             try
   965             {
   966                 // Initialize MAPI
   967                 NativeMethods.MAPIInitialize(IntPtr.Zero);
   968 
   969                 // Get the IUnknown interface from the MAPI object
   970                 IUnknown = Marshal.GetIUnknownForObject(mapiObject);
   971 
   972                 // Set the MAPI object interface GUID that we pass to retrieve the IMessage interface.
   973                 Guid guidIMessage = new Guid(mapiInterfaceId);
   974 
   975                 // Try to retrieve the MAPI object interface
   976                 if (Marshal.QueryInterface(IUnknown, ref guidIMessage, out IMAPIObject) != (uint)Mapi.HResult.S_OK)
   977                 {
   978                     Log.Error("GetMAPIProperty: Could not retrieve IMessage interface. Returning null.");
   979                     return null;
   980                 }
   981 
   982                 // Set the IMAPIProp interface GUID that we pass to retrieve the IMAPIProp Interface.
   983                 Guid guidIMAPIProp = new Guid(Mapi.MAPIInterfaceIds.IMAPIProp);
   984 
   985                 // Try to retrieve the IMAPIProp interface
   986                 if ((Marshal.QueryInterface(IMAPIObject, ref guidIMAPIProp, out IMAPIProp) != (uint)Mapi.HResult.S_OK) ||
   987                     (IMAPIProp == IntPtr.Zero))
   988                 {
   989                     Log.Error("GetMAPIProperty: Could not retrieve IMAPIProp interface. Returning null.");
   990                     return null;
   991                 }
   992 
   993                 // Try to get the property
   994                 NativeMethods.HrGetOneProp(IMAPIProp, mapiProperty.Tag, out ptrPropValue);
   995 
   996                 if (ptrPropValue == IntPtr.Zero)
   997                 {
   998                     Log.Error("GetMAPIProperty: Could not retrieve pointer to property value. Returning null.");
   999                     return null;
  1000                 }
  1001 
  1002                 // Get the SPropValue structure
  1003                 sPropValue = (SPropValue)Marshal.PtrToStructure(ptrPropValue, typeof(SPropValue));
  1004 
  1005                 // Convert the retrieved value
  1006                 propertyValue = Mapi.ConvertSPropValueToObject(mapiProperty, sPropValue);
  1007             }
  1008             catch (Exception ex)
  1009             {
  1010                 propertyValue = null;
  1011                 Log.Error("GetMAPIProperty: Error occured. " + ex.ToString());
  1012             }
  1013             finally
  1014             {
  1015                 // Free used memory structures
  1016                 if (ptrPropValue != IntPtr.Zero)
  1017                 {
  1018                     NativeMethods.MAPIFreeBuffer(ptrPropValue);
  1019                 }
  1020 
  1021                 // Clean up all references to COM Objects
  1022                 if (IMAPIProp != IntPtr.Zero)
  1023                 {
  1024                     Marshal.Release(IMAPIProp);
  1025                 }
  1026 
  1027                 if (IMAPIObject != IntPtr.Zero)
  1028                 {
  1029                     Marshal.Release(IMAPIObject);
  1030                 }
  1031 
  1032                 if (IUnknown != IntPtr.Zero)
  1033                 {
  1034                     Marshal.Release(IUnknown);
  1035                 }
  1036 
  1037                 NativeMethods.MAPIUninitialize();
  1038             }
  1039 
  1040             return propertyValue;
  1041         }
  1042 
  1043         /// <summary>
  1044         /// Gets a set of MAPI property values from an Outlook item.
  1045         /// </summary>
  1046         /// <param name="mapiObject">The MAPIOBJECT property of the Outlook item to get the property values for.</param>
  1047         /// <param name="mapiProperties">The MAPI properties to get their values for.</param>
  1048         /// <param name="propertyValues">The retrieved values.</param>
  1049         /// <returns>The error code returned by IMAPIProp::GetProps or 0 if no error occured.</returns>
  1050         internal static int GetMAPIProperties(object mapiObject, List<MapiProperty.MapiProp> mapiProperties, out MAPIProperties propertyValues)
  1051         {
  1052             int result = (int)Mapi.HResult.S_FALSE;
  1053             propertyValues = null;
  1054 
  1055             // Get MAPI object from mail item
  1056             if (mapiObject == null)
  1057             {
  1058                 Log.Error("GetMAPIProperties: MAPI object is null. Property could not be set.");
  1059                 return result;
  1060             }
  1061 
  1062             // Check if we actually have some properties
  1063             int propertiesCount = mapiProperties.Count;
  1064             if (propertiesCount < 1)
  1065             {
  1066                 Log.Error("GetMAPIProperties: No properties found.");
  1067                 return result;
  1068             }
  1069 
  1070             // Pointer to IUnknown interface
  1071             IntPtr IUnknown = IntPtr.Zero;
  1072 
  1073             // Pointer to the MAPI properties array
  1074             IntPtr propTagArray = IntPtr.Zero;
  1075 
  1076             // Pointer to the value
  1077             IntPtr valuePtr = IntPtr.Zero;
  1078 
  1079             try
  1080             {
  1081                 // Initialize MAPI
  1082                 NativeMethods.MAPIInitialize(IntPtr.Zero);
  1083 
  1084                 // Get the IUnknown interface from the MAPI object
  1085                 IUnknown = Marshal.GetIUnknownForObject(mapiObject);
  1086 
  1087                 // Get the IMAPIProp interface
  1088                 Mapi.IMAPIProp IMAPIProp = (Mapi.IMAPIProp)Marshal.GetTypedObjectForIUnknown(IUnknown, typeof(Mapi.IMAPIProp));
  1089 
  1090                 // Create the SPropValue structure
  1091                 Mapi.SPropValue sPropValue = new Mapi.SPropValue();
  1092                 int propValueSize = Marshal.SizeOf(sPropValue);
  1093 
  1094                 // Allocate memory for the array of SPropValues to set
  1095                 propTagArray = Marshal.AllocHGlobal(propValueSize * propertiesCount);
  1096 
  1097                 // Create the property values array
  1098                 uint[] propertyTags = new uint[propertiesCount + 1];
  1099                 propertyTags[0] = (uint)propertiesCount;
  1100                 for (int i = 0; i < propertiesCount; i++)
  1101                 {
  1102                     propertyTags[i + 1] = (uint)mapiProperties[i].Tag;
  1103                 }
  1104 
  1105                 // Get properties
  1106                 result = IMAPIProp.GetProps(propertyTags, Mapi.MAPI_UNICODE, out uint valuesCount, out IntPtr propArray);
  1107 
  1108                 // Convert the retrieved values
  1109                 object[] values = new object[valuesCount];
  1110                 for (int i = 0; i < valuesCount; i++)
  1111                 {
  1112                     sPropValue = (SPropValue)Marshal.PtrToStructure((propArray + (i * propValueSize)), typeof(SPropValue));
  1113                     values[i] = Mapi.ConvertSPropValueToObject(mapiProperties[i], sPropValue);
  1114                 }
  1115 
  1116                 // Check if returned values match properties count
  1117                 if (propertiesCount != valuesCount)
  1118                 {
  1119                     Log.Error("GetMAPIProperties: Properties count doesn't match values count.");
  1120                     return result;
  1121                 }
  1122 
  1123                 // Create return dictionary
  1124                 propertyValues = new MAPIProperties();
  1125                 for (int i = 0; i < valuesCount; i++)
  1126                 {
  1127                     propertyValues.Add(mapiProperties[i], values[i]);
  1128                 }
  1129             }
  1130             catch (Exception ex)
  1131             {
  1132                 Log.Error("SetMAPIProperties: Error occured. " + ex.ToString());
  1133             }
  1134             finally
  1135             {
  1136                 if (IUnknown != IntPtr.Zero)
  1137                 {
  1138                     Marshal.Release(IUnknown);
  1139                 }
  1140 
  1141                 if (propTagArray != IntPtr.Zero)
  1142                 {
  1143                     Marshal.FreeHGlobal(propTagArray);
  1144                 }
  1145 
  1146                 if (valuePtr != IntPtr.Zero)
  1147                 {
  1148                     Marshal.FreeHGlobal(valuePtr);
  1149                 }
  1150 
  1151                 NativeMethods.MAPIUninitialize();
  1152             }
  1153 
  1154             return result;
  1155         }
  1156 
  1157         /// <summary>
  1158         /// Gets a list of all table rows and their MAPI properties using the IMAPITable.QueryRows method.
  1159         /// Each list entry in the return value corresponds to a table row, while each list entry's dictionary
  1160         /// is a set of property value pairs returned for the table row.
  1161         /// This code is based on Fred Song's Managed MAPI: 
  1162         /// https://www.codeproject.com/Articles/455823/Managed-MAPI-Part-1-Logon-MAPI-Session-and-Retriev
  1163         /// </summary>
  1164         /// <param name="mapiTable">The table to query its rows.</param>
  1165         /// <param name="rowCount">The maximum count of rows to query.</param>
  1166         /// <param name="values">The returned values.</param>
  1167         /// <returns>The status code of the IMAPITable.QueryRows method.</returns>
  1168         public static int GetTableProperties(Mapi.IMAPITable mapiTable, int rowCount, out List<Dictionary<uint, object>> properties)
  1169         {
  1170             IntPtr pRowSet = IntPtr.Zero;
  1171             properties = null;
  1172 
  1173             int status = mapiTable.QueryRows(rowCount, 0, out pRowSet);
  1174             if (status != (uint)Mapi.HResult.S_OK)
  1175             {
  1176                 NativeMethods.MAPIFreeBuffer(pRowSet);
  1177             }
  1178 
  1179             uint cRows = (uint)Marshal.ReadInt32(pRowSet);
  1180             if (cRows < 1)
  1181             {
  1182                 NativeMethods.MAPIFreeBuffer(pRowSet);
  1183                 return status;
  1184             }
  1185 
  1186             int pIntSize = IntPtr.Size, intSize = Marshal.SizeOf(typeof(Int32));
  1187             int sizeOfSRow = 2 * intSize + pIntSize;
  1188             IntPtr rows = pRowSet + intSize;
  1189             properties = new List<Dictionary<uint, object>>();
  1190             for (int i = 0; i < cRows; i++)
  1191             {
  1192                 IntPtr pRowOffset = rows + i * sizeOfSRow;
  1193                 uint cValues = (uint)Marshal.ReadInt32(pRowOffset + pIntSize);
  1194                 IntPtr pProps = Marshal.ReadIntPtr(pRowOffset + pIntSize + intSize);
  1195                 
  1196                 int size =  Marshal.SizeOf(typeof(SPropValue));
  1197 
  1198                 Dictionary<uint, object> propValues = new Dictionary<uint, object>();
  1199                 for (int j = 0; j < cValues; j++) // each column
  1200                 {
  1201                     SPropValue lpProp = (SPropValue)Marshal.PtrToStructure((pProps + (j * size)), typeof(SPropValue));
  1202                     propValues.Add(lpProp.PropTag, lpProp.Value);
  1203                 }
  1204 
  1205                 properties.Add(propValues);
  1206             }
  1207 
  1208             NativeMethods.MAPIFreeBuffer(pRowSet);
  1209             return status;
  1210         }
  1211 
  1212         /// <summary>
  1213         /// Sets the given MAPI properties on the Outlook mail item.
  1214         /// Note: the caller has to make sure that the count of MAPI properties
  1215         /// and respective values matches and that the given values match the
  1216         /// required data type for each MAPI property.
  1217         /// Do not call this method from a background thread!
  1218         /// </summary>
  1219         /// <param name="omi">The Outlook mail item to set its properties.</param>
  1220         /// <param name="mapiProperties">The MAPI properties to set.</param>
  1221         /// <param name="values">The values to set.</param>
  1222         /// <returns>True if the method succeeds, otherwise false.</returns>
  1223         public static bool SetMAPIProperties(Outlook.MailItem omi, MapiProperty.MapiProp[] mapiProperties, object[] values)
  1224         {
  1225             // The return status of this method
  1226             bool success = false;
  1227 
  1228             // Pointer to IUnknown interface
  1229             IntPtr IUnknown = IntPtr.Zero;
  1230 
  1231             // Pointer to the MAPI properties array
  1232             IntPtr propArray = IntPtr.Zero;
  1233 
  1234             // Pointer to the value
  1235             IntPtr valuePtr = IntPtr.Zero;
  1236 
  1237             // Get MAPI object from mail item
  1238             object mapiObject = omi?.MAPIOBJECT;
  1239             if (mapiObject == null)
  1240             {
  1241                 Log.Error("SetMAPIProperties: MAPI object is null. Property could not be set.");
  1242                 return success;
  1243             }
  1244 
  1245             // Make sure the properties count matches the values count
  1246             int propCount = mapiProperties.Length;
  1247             if (propCount != values.Length)
  1248             {
  1249                 Log.Error("SetMAPIProperties: Mismatch between tag count and value count.");
  1250                 return success;
  1251             }
  1252 
  1253             try
  1254             {
  1255                 // Initialize MAPI
  1256                 NativeMethods.MAPIInitialize(IntPtr.Zero);
  1257 
  1258                 // Get the IUnknown interface from the MAPI object
  1259                 IUnknown = Marshal.GetIUnknownForObject(mapiObject);
  1260 
  1261                 // Get the IMAPIProp interface
  1262                 Mapi.IMAPIProp IMAPIProp = (Mapi.IMAPIProp)Marshal.GetTypedObjectForIUnknown(IUnknown, typeof(Mapi.IMAPIProp));
  1263 
  1264                 // Create the SPropValue structure
  1265                 Mapi.SPropValue sPropValue = new Mapi.SPropValue();
  1266                 var propValueSize = Marshal.SizeOf(sPropValue);
  1267 
  1268                 // Allocate memory for the array of SPropValues to set
  1269                 propArray = Marshal.AllocHGlobal(propValueSize * propCount);
  1270 
  1271                 // Create the property values array
  1272                 for (int i = 0; i < propCount; i++)
  1273                 {
  1274                     // Get the current property/value pair
  1275                     MapiProperty.MapiProp mapiProperty = mapiProperties[i];
  1276                     uint tag = mapiProperty.Tag;
  1277                     object value = values[i];
  1278 
  1279                     // Set the property value's property tag
  1280                     sPropValue.PropTag = (uint)tag;
  1281 
  1282                     // Set the property value's value
  1283                     switch (mapiProperty.DataType)
  1284                     {
  1285                         case MapiProperty.MapiDataType.PtypBoolean:
  1286                             {
  1287                                 try
  1288                                 {
  1289                                     sPropValue.Value.b = (ushort)Convert.ToInt16((bool)value);
  1290                                 }
  1291                                 catch (Exception ex)
  1292                                 {
  1293                                     throw new Exception(string.Format("Error converting to Boolean. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1294                                 }
  1295                             }
  1296                             break;
  1297                         case MapiProperty.MapiDataType.PtypString:
  1298                             {
  1299                                 try
  1300                                 {
  1301                                     valuePtr = Marshal.StringToHGlobalUni(value as string);
  1302                                     sPropValue.Value.lpszW = valuePtr;
  1303                                 }
  1304                                 catch (Exception ex)
  1305                                 {
  1306                                     throw new Exception(string.Format("Error converting to String. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1307                                 }
  1308                             }
  1309                             break;
  1310                         case MapiProperty.MapiDataType.PtypString8:
  1311                             {
  1312                                 try
  1313                                 {
  1314                                     valuePtr = Marshal.StringToHGlobalAnsi(value as string);
  1315                                     sPropValue.Value.lpszA = valuePtr;
  1316                                 }
  1317                                 catch (Exception ex)
  1318                                 {
  1319                                     throw new Exception(string.Format("Error converting to String8. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1320                                 }
  1321                             }
  1322                             break;
  1323                         case MapiProperty.MapiDataType.PtypInteger16:
  1324                             {
  1325                                 try
  1326                                 {
  1327                                     sPropValue.Value.i = (short)value;
  1328                                 }
  1329                                 catch (Exception ex)
  1330                                 {
  1331                                     throw new Exception(string.Format("Error converting to Short. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1332                                 }
  1333                             }
  1334                             break;
  1335                         case MapiProperty.MapiDataType.PtypInteger32:
  1336                             {
  1337                                 try
  1338                                 {
  1339                                     sPropValue.Value.l = (int)value;
  1340                                 }
  1341                                 catch (Exception ex)
  1342                                 {
  1343                                     throw new Exception(string.Format("Error converting to Integer. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1344                                 }
  1345                             }
  1346                             break;
  1347                         case MapiProperty.MapiDataType.PtypFloating32:
  1348                             {
  1349                                 try
  1350                                 {
  1351                                     sPropValue.Value.flt = (float)value;
  1352                                 }
  1353                                 catch (Exception ex)
  1354                                 {
  1355                                     throw new Exception(string.Format("Error converting to Float. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1356                                 }
  1357                             }
  1358                             break;
  1359                         case MapiProperty.MapiDataType.PtypFloating64:
  1360                         case MapiProperty.MapiDataType.PtypFloatingTime:
  1361                             {
  1362                                 try
  1363                                 {
  1364                                     sPropValue.Value.at = (double)value;
  1365                                 }
  1366                                 catch (Exception ex)
  1367                                 {
  1368                                     throw new Exception(string.Format("Error converting to Double. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1369                                 }
  1370                             }
  1371                             break;
  1372                         case MapiProperty.MapiDataType.PtypInteger64:
  1373                             {
  1374                                 try
  1375                                 {
  1376                                     sPropValue.Value.li = (ulong)value;
  1377                                 }
  1378                                 catch (Exception ex)
  1379                                 {
  1380                                     throw new Exception(string.Format("Error converting to Ulong. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1381                                 }
  1382                             }
  1383                             break;
  1384                         case MapiProperty.MapiDataType.PtypGuid:
  1385                             {
  1386                                 try
  1387                                 {
  1388                                     IntPtr guidPtr = Marshal.AllocHGlobal(Marshal.SizeOf(value));
  1389                                     Marshal.StructureToPtr((Guid)value, guidPtr, false);
  1390                                     sPropValue.Value.lpguid = guidPtr;
  1391                                 }
  1392                                 catch (Exception ex)
  1393                                 {
  1394                                     throw new Exception(string.Format("Error converting to Guid. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1395                                 }
  1396                             }
  1397                             break;
  1398                         case MapiProperty.MapiDataType.PtypBinary:
  1399                             {
  1400                                 try
  1401                                 {
  1402                                     sPropValue.Value.bin = (Mapi.SRowSet)value;
  1403                                 }
  1404                                 catch (Exception ex)
  1405                                 {
  1406                                     throw new Exception(string.Format("Error converting to Binary. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1407                                 }
  1408                             }
  1409                             break;
  1410                         default:
  1411                             {
  1412                                 throw new Exception(string.Format("Error creating SPropValue. Data type {0} not supported.", Enum.GetName(typeof(MapiProperty.MapiDataType), mapiProperty.DataType)));
  1413                             }
  1414                     }
  1415 
  1416                     Marshal.StructureToPtr(sPropValue, (IntPtr)((uint)propArray + (propValueSize * i)), false);
  1417 
  1418                     if (valuePtr != IntPtr.Zero)
  1419                     {
  1420                         Marshal.FreeHGlobal(valuePtr);
  1421                     }
  1422                 }
  1423 
  1424                 // Set properties and save
  1425                 IntPtr problems = IntPtr.Zero;
  1426                 if (IMAPIProp.SetProps((uint)propCount, propArray, problems) == (uint)Mapi.HResult.S_OK)
  1427                 {
  1428                     success = (IMAPIProp.SaveChanges((uint)Mapi.SaveOption.KEEP_OPEN_READWRITE) == (uint)Mapi.HResult.S_OK);
  1429                 }
  1430             }
  1431             catch (Exception ex)
  1432             {
  1433                 Log.Error("SetMAPIProperties: Error occured. " + ex.ToString());
  1434                 success = false;
  1435             }
  1436             finally
  1437             {
  1438                 if (IUnknown != IntPtr.Zero)
  1439                 {
  1440                     Marshal.Release(IUnknown);
  1441                 }
  1442 
  1443                 if (propArray != IntPtr.Zero)
  1444                 {
  1445                     Marshal.FreeHGlobal(propArray);
  1446                 }
  1447 
  1448                 if (valuePtr != IntPtr.Zero)
  1449                 {
  1450                     Marshal.FreeHGlobal(valuePtr);
  1451                 }
  1452 
  1453                 NativeMethods.MAPIUninitialize();
  1454             }
  1455 
  1456             return success;
  1457         }
  1458 
  1459         /// <summary>
  1460         /// Sets the given MAPI property on the Outlook mail item.
  1461         /// Note: the caller has to make sure that the given value matches the
  1462         /// required data type for the MAPI property.
  1463         /// Do not call this method from a background thread!
  1464         /// </summary>
  1465         /// <param name="omi">The Outlook mail item to set the property for.</param>
  1466         /// <param name="mapiProperties">The MAPI property to set.</param>
  1467         /// <param name="values">The value to set.</param>
  1468         /// <param name="useSetProps">Whether to use the IMAPIProp.SetProps method (instead of HrSetOneProp).</param>
  1469         /// <returns>True if the method succeeds, otherwise false.</returns>
  1470         public static bool SetMAPIProperty(Outlook.MailItem omi,
  1471                                            MapiProperty.MapiProp mapiProperty,
  1472                                            object value,
  1473                                            bool useSetProps = false)
  1474         {
  1475             if (useSetProps)
  1476             {
  1477                 return Mapi.SetMAPIProperties(omi, new MapiProperty.MapiProp[] { mapiProperty }, new object[] { value });
  1478             }
  1479 
  1480             // The return status of this method
  1481             bool success = false;
  1482 
  1483             // Pointer to IUnknown interface
  1484             IntPtr IUnknown = IntPtr.Zero;
  1485 
  1486             // Pointer to IMessage interface
  1487             IntPtr IMessage = IntPtr.Zero;
  1488 
  1489             // Pointer to IMAPIProp interface
  1490             IntPtr IMAPIProp = IntPtr.Zero;
  1491 
  1492             // Structure that will hold the property value
  1493             Mapi.SPropValue sPropValue;
  1494 
  1495             // A pointer that points to the SPropValue structure 
  1496             IntPtr ptrPropValue = IntPtr.Zero;
  1497 
  1498             // Pointer to the value
  1499             IntPtr valuePtr = IntPtr.Zero;
  1500 
  1501             // Get MAPI object from mail item
  1502             object mapiObject = omi?.MAPIOBJECT;
  1503             if (mapiObject == null)
  1504             {
  1505                 Log.Error("SetMAPIProperty: MAPI object is null. Property could not be set.");
  1506                 return success;
  1507             }
  1508 
  1509             try
  1510             {
  1511                 // Initialize MAPI
  1512                 NativeMethods.MAPIInitialize(IntPtr.Zero);
  1513 
  1514                 // Get the IUnknown interface from the MAPI object
  1515                 IUnknown = Marshal.GetIUnknownForObject(mapiObject);
  1516 
  1517                 // Set the IMessage interface GUID that we pass to retrieve the IMessage interface.
  1518                 Guid guidIMessage = new Guid(Mapi.MAPIInterfaceIds.IMessage);
  1519 
  1520                 // Try to retrieve the IMessage interface
  1521                 if (Marshal.QueryInterface(IUnknown, ref guidIMessage, out IMessage) != (uint)Mapi.HResult.S_OK)
  1522                 {
  1523                     Log.Error("SetMAPIProperty: Could not retrieve IMessage interface. Property could not be set.");
  1524                     return success;
  1525                 }
  1526 
  1527                 // Set the IMAPIProp interface GUID that we pass to retrieve the IMAPIProp Interface.
  1528                 Guid guidIMAPIProp = new Guid(Mapi.MAPIInterfaceIds.IMAPIProp);
  1529 
  1530                 // Try to retrieve the IMAPIProp interface
  1531                 if ((Marshal.QueryInterface(IMessage, ref guidIMAPIProp, out IMAPIProp) != (uint)Mapi.HResult.S_OK) ||
  1532                     (IMAPIProp == IntPtr.Zero))
  1533                 {
  1534                     Log.Error("SetMAPIProperty: Could not retrieve IMAPIProp interface. Property could not be set.");
  1535                     return success;
  1536                 }
  1537 
  1538                 // Create the SPropValue structure
  1539                 sPropValue = new Mapi.SPropValue
  1540                 {
  1541                     PropTag = (uint)mapiProperty.Tag
  1542                 };
  1543 
  1544                 // Set the property value's value
  1545                 switch (mapiProperty.DataType)
  1546                 {
  1547                     case MapiProperty.MapiDataType.PtypBoolean:
  1548                         {
  1549                             try
  1550                             {
  1551                                 sPropValue.Value.b = (ushort)Convert.ToInt16((bool)value);
  1552                             }
  1553                             catch (Exception ex)
  1554                             {
  1555                                 throw new Exception(string.Format("Error converting to Boolean. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1556                             }
  1557                         }
  1558                         break;
  1559                     case MapiProperty.MapiDataType.PtypString:
  1560                         {
  1561                             try
  1562                             {
  1563                                 valuePtr = Marshal.StringToHGlobalUni(value as string);
  1564                                 sPropValue.Value.lpszW = valuePtr;
  1565                             }
  1566                             catch (Exception ex)
  1567                             {
  1568                                 throw new Exception(string.Format("Error converting to String. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1569                             }
  1570                         }
  1571                         break;
  1572                     case MapiProperty.MapiDataType.PtypString8:
  1573                         {
  1574                             try
  1575                             {
  1576                                 valuePtr = Marshal.StringToHGlobalAnsi(value as string);
  1577                                 sPropValue.Value.lpszA = valuePtr;
  1578                             }
  1579                             catch (Exception ex)
  1580                             {
  1581                                 throw new Exception(string.Format("Error converting to String8. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1582                             }
  1583                         }
  1584                         break;
  1585                     case MapiProperty.MapiDataType.PtypInteger16:
  1586                         {
  1587                             try
  1588                             {
  1589                                 sPropValue.Value.i = (short)value;
  1590                             }
  1591                             catch (Exception ex)
  1592                             {
  1593                                 throw new Exception(string.Format("Error converting to Short. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1594                             }
  1595                         }
  1596                         break;
  1597                     case MapiProperty.MapiDataType.PtypInteger32:
  1598                         {
  1599                             try
  1600                             {
  1601                                 sPropValue.Value.l = (int)value;
  1602                             }
  1603                             catch (Exception ex)
  1604                             {
  1605                                 throw new Exception(string.Format("Error converting to Integer. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1606                             }
  1607                         }
  1608                         break;
  1609                     case MapiProperty.MapiDataType.PtypFloating32:
  1610                         {
  1611                             try
  1612                             {
  1613                                 sPropValue.Value.flt = (float)value;
  1614                             }
  1615                             catch (Exception ex)
  1616                             {
  1617                                 throw new Exception(string.Format("Error converting to Float. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1618                             }
  1619                         }
  1620                         break;
  1621                     case MapiProperty.MapiDataType.PtypFloating64:
  1622                     case MapiProperty.MapiDataType.PtypFloatingTime:
  1623                         {
  1624                             try
  1625                             {
  1626                                 sPropValue.Value.at = (double)value;
  1627                             }
  1628                             catch (Exception ex)
  1629                             {
  1630                                 throw new Exception(string.Format("Error converting to Double. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1631                             }
  1632                         }
  1633                         break;
  1634                     case MapiProperty.MapiDataType.PtypInteger64:
  1635                         {
  1636                             try
  1637                             {
  1638                                 sPropValue.Value.li = (ulong)value;
  1639                             }
  1640                             catch (Exception ex)
  1641                             {
  1642                                 throw new Exception(string.Format("Error converting to Ulong. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1643                             }
  1644                         }
  1645                         break;
  1646                     case MapiProperty.MapiDataType.PtypGuid:
  1647                         {
  1648                             try
  1649                             {
  1650                                 IntPtr guidPtr = Marshal.AllocHGlobal(Marshal.SizeOf(value));
  1651                                 Marshal.StructureToPtr((Guid)value, guidPtr, false);
  1652                                 sPropValue.Value.lpguid = guidPtr;
  1653                             }
  1654                             catch (Exception ex)
  1655                             {
  1656                                 throw new Exception(string.Format("Error converting to Guid. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1657                             }
  1658                         }
  1659                         break;
  1660                     case MapiProperty.MapiDataType.PtypBinary:
  1661                         {
  1662                             try
  1663                             {
  1664                                 sPropValue.Value.bin = (Mapi.SRowSet)value;
  1665                             }
  1666                             catch (Exception ex)
  1667                             {
  1668                                 throw new Exception(string.Format("Error converting to Binary. Property tag: {0}. Value: {1}. Exception: {2}.", mapiProperty.DaslName, value?.ToString(), ex.ToString()));
  1669                             }
  1670                         }
  1671                         break;
  1672                     default:
  1673                         {
  1674                             throw new Exception(string.Format("Error creating SPropValue. Data type {0} not supported.", Enum.GetName(typeof(MapiProperty.MapiDataType), mapiProperty.DataType)));
  1675                         }
  1676                 }
  1677 
  1678                 // Get pointer to property value structure
  1679                 ptrPropValue = Marshal.AllocHGlobal(Marshal.SizeOf(sPropValue));
  1680                 Marshal.StructureToPtr(sPropValue, ptrPropValue, false);
  1681 
  1682                 // Try to set the property
  1683                 NativeMethods.HrSetOneProp(IMAPIProp, ptrPropValue);
  1684 
  1685                 // Save changes
  1686                 Mapi.IMAPIProp mapiProp = (Mapi.IMAPIProp)Marshal.GetTypedObjectForIUnknown(IUnknown, typeof(Mapi.IMAPIProp));
  1687                 success = (mapiProp.SaveChanges((uint)Mapi.SaveOption.KEEP_OPEN_READWRITE) == (uint)Mapi.HResult.S_OK);
  1688             }
  1689             catch (Exception ex)
  1690             {
  1691                 Log.Error("SetMAPIProperty: Error occured. " + ex.ToString());
  1692                 success = false;
  1693             }
  1694             finally
  1695             {
  1696                 // Free used memory structures
  1697                 if (ptrPropValue != IntPtr.Zero)
  1698                 {
  1699                     NativeMethods.MAPIFreeBuffer(ptrPropValue);
  1700                 }
  1701 
  1702                 // Clean up all references to COM Objects
  1703                 if (IMAPIProp != IntPtr.Zero)
  1704                 {
  1705                     Marshal.Release(IMAPIProp);
  1706                 }
  1707 
  1708                 if (IMessage != IntPtr.Zero)
  1709                 {
  1710                     Marshal.Release(IMessage);
  1711                 }
  1712 
  1713                 if (IUnknown != IntPtr.Zero)
  1714                 {
  1715                     Marshal.Release(IUnknown);
  1716                 }
  1717 
  1718                 NativeMethods.MAPIUninitialize();
  1719             }
  1720 
  1721             return success;
  1722         }
  1723 
  1724         /// <summary>
  1725         /// Calls SaveChanges() with the given flag.
  1726         /// </summary>
  1727         /// <param name="omi">The Outlook mail item to save.</param>
  1728         /// <param name="saveOption">The save flag to pass.</param>
  1729         /// <returns>The status of this method.</returns>
  1730         public static int Save(Outlook.MailItem omi, Mapi.SaveOption saveOption)
  1731         {
  1732             int status = (int)Mapi.HResult.S_FALSE;
  1733 
  1734             // Pointer to IUnknown interface
  1735             IntPtr IUnknown = IntPtr.Zero;
  1736 
  1737             // A pointer that points to the SPropValue structure 
  1738             IntPtr ptrPropValue = IntPtr.Zero;
  1739 
  1740             // Get MAPI object from mail item
  1741             object mapiObject = omi?.MAPIOBJECT;
  1742             if (mapiObject == null)
  1743             {
  1744                 Log.Error("SetMAPIProperty: MAPI object is null. Property could not be set.");
  1745                 return status;
  1746             }
  1747 
  1748             try
  1749             {
  1750                 // Initialize MAPI
  1751                 NativeMethods.MAPIInitialize(IntPtr.Zero);
  1752 
  1753                 // Get the IUnknown interface from the MAPI object
  1754                 IUnknown = Marshal.GetIUnknownForObject(mapiObject);
  1755 
  1756                 // Save changes
  1757                 Mapi.IMAPIProp mapiProp = (Mapi.IMAPIProp)Marshal.GetTypedObjectForIUnknown(IUnknown, typeof(Mapi.IMAPIProp));
  1758                 status = mapiProp.SaveChanges((uint)saveOption);
  1759             }
  1760             catch (Exception ex)
  1761             {
  1762                 Log.Error("SetMAPIProperty: Error occured. " + ex.ToString());
  1763                 status = (int)Mapi.HResult.S_FALSE;
  1764             }
  1765             finally
  1766             {
  1767                 // Free used memory structures
  1768                 if (ptrPropValue != IntPtr.Zero)
  1769                 {
  1770                     NativeMethods.MAPIFreeBuffer(ptrPropValue);
  1771                 }
  1772 
  1773                 // Clean up all references to COM Objects
  1774                 if (IUnknown != IntPtr.Zero)
  1775                 {
  1776                     Marshal.Release(IUnknown);
  1777                 }
  1778 
  1779                 NativeMethods.MAPIUninitialize();
  1780             }
  1781 
  1782             return status;
  1783         }
  1784         #endregion
  1785     }
  1786 }