Following up on my previous article on CONNECT_IGNORE_NO_PF, I had a customer recently who claimed MAPI didn't work if there is no Public Folder store. They kept getting MAPI_E_FAILONEPROVIDER when they tried to open the message store. I pointed them to CONNECT_IGNORE_NO_PF, but they were aware of it and insisted they were setting it right. They even showed my their code, and I confirmed that they were setting it. I didn't quite trust their code, so I used MFCMAPI to test. Sure enough - on a brand new lab setup with no Public Folder store, I saw the same thing they did!
What the customer was doing running their code as the Administrator account, and trying to log on to another mailbox. We'll call that one Bart Simpson (bsimpson). The key to solving this issue was when I realized I could log on to Administrator's mailbox just fine, and even jump from there into Bart's mailbox - it was only when I created a profile for Bart and tried to use it that I got the error. Maybe it's just rights?
Here were the permissions Administrator had to Bart's mailbox:
[PS] C:\>get-MailboxPermission -identity bsimpson -User administrator |fl AccessRights : {FullAccess} Deny : True InheritanceType : All User : MyDOM\Administrator Identity : MyDOM.extest.microsoft.com/Users/Bart Simpson IsInherited : True IsValid : True ObjectState : Unchanged AccessRights : {FullAccess, DeleteItem, ReadPermission, ChangePermission, ChangeOwner} Deny : False InheritanceType : All User : MyDOM\Administrator Identity : MyDOM.extest.microsoft.com/Users/Bart Simpson IsInherited : True IsValid : True ObjectState : Unchanged
Note the deny there on the FullAccess right! Well no wonder I couldn't open the mailbox!
There are two ways to resolve this:
[PS] C:\>Add-MailboxPermission -identity bsimpson -accessRights fullAccess -User administrator |fl AccessRights : {FullAccess} Deny : False InheritanceType : All User : MyDOM\Administrator Identity : MyDOM.extest.microsoft.com/Users/Bart Simpson IsInherited : False IsValid : True ObjectState : Unchanged
[PS] C:\>get-MailboxPermission -identity bsimpson -User administrator |fl AccessRights : {FullAccess} Deny : False InheritanceType : All User : MyDOM\Administrator Identity : MyDOM.extest.microsoft.com/Users/Bart Simpson IsInherited : False IsValid : True ObjectState : Unchanged AccessRights : {FullAccess} Deny : True InheritanceType : All User : MyDOM\Administrator Identity : MyDOM.extest.microsoft.com/Users/Bart Simpson IsInherited : True IsValid : True ObjectState : Unchanged AccessRights : {FullAccess, DeleteItem, ReadPermission, ChangePermission, ChangeOwner} Deny : False InheritanceType : All User : MyDOM\Administrator Identity : MyDOM.extest.microsoft.com/Users/Bart Simpson IsInherited : True IsValid : True ObjectState : Unchanged
Now - what was wild about this case was that even after granting the rights, the customer was still getting MAPI_E_FAILONEPROVIDER when connecting to an Exchange 2007 server that had no Public Folders. Even when they tried to log on to Administrator's mailbox while running as Administrator they got the error.
This prompted another, much closer, look at the source code they had sent me, where I realized they were trying to open the first message store in the message store table, which happened to be the Public Folder store. Well of course that's going to fail!
After removing this call, everything worked as described.
FURTHER READING The way permissions work here didn't change in Exchange 2007. Here are some articles documenting it for Exchange 2003 and 2000: How to configure an account to use the ExMerge utility in Exchange 2000 Server and in Exchange Server 2003 How to assign service account access to all mailboxes in Exchange Server 2003
Most of you have realized by now that Exchange 2007 doesn't include MAPI "in the box". We're supporting two ways to put MAPI on an Exchange 2007 server:
Some of you have noticed as well that with Exchange 2007, you don't have to have a Public Folder store. What happens when you try to connect to an Exchange 2007 server that doesn't have a Public Folder store?
With Outlook 2007, you connect with no problem. Earlier versions of Outlook get this error:
"Your Exchange Server administrator has blocked the version of Outlook that you are using. Contact your administrator for assistance."
MAPI applications will get the error MAPI_E_FAILONEPROVIDER if they're using any version of MAPI except the version supplied by Outlook 2007.
So what's a developer to do? Are you locked in to Outlook 2007's MAPI? Why did we provide a MAPI download if it won't even work?
Introducing CONNECT_IGNORE_NO_PF
Exchange 2007 blocks all MAPI providers earlier than Outlook 2007 when Public Folders are not available. This is because all of the earlier clients assume Public Folders will be available and will experience failures if allowed to connect. For customers whose code does not depend on Public Folders, a new flag has been made available to allow this check to be bypassed.
Right now, the only version of MAPI which supports this new flag is the MAPI download.
Details on the new flag:
#define CONNECT_IGNORE_NO_PF ((ULONG)0x8000)
This is set in the PR_PROFILE_CONNECT_FLAGS property on the global profile section.
The global profile section can be accessed by calling OpenProfileSection with pbGlobalProfileSectionGuid, which is defined in edkmdb.h.
When this flag is set, Exchange 2007 will not check if public folders are available when logging on. Use of this flag can be demonstrated with MAPI Editor:
Caveats
Update - 4/17/07 Removed inadvertent assertion that there is a build of Exchange 2003 MAPI that understands this flag. If such a build does become available I'll update again.
[This is now documented here: http://msdn.microsoft.com/en-us/library/bb821125.aspx]
(Now that Outlook 2007 is available I'm reposting some of the articles from the Outlook 2007 Beta Documentation series. Some of the articles are virtually unchanged, others are completely different to reflect the differences between Beta and RTM. This article finishes the rewrite of Outlook 2007 Beta Documentation - Notification Based Indexing Support.)
The MAPI PH consults the registry to determine which MAPI providers should be indexed. Any provider not listed in the registry will not be indexed by the MAPI PH.
Outlook adds the following 4 providers to the registry to enable indexing if the policy allows it:
Third party providers which wish to be indexed will need to list themselves in the registry.
The MAPI PH will check the following registry values:
Only one value has to be set and they are checked in the order shown above.
When the MAPI PH is installed, the default providers are configured as follows:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Search\Preferences] "{4154494E-BFF9-01B8-00AA-0037D96E0000}"=dword:00000001 "{C0A19454-7F29-1B10-A587-08002B2A2517}"=dword:00000001 "{70fab278-f7af-cd11-9bc8-00aa002fc45a}"=dword:00000001 "{c34f5c97-eb05-bb4b-b199-2a7570ec7cf9}"=dword:00000001
The guids shown are the PR_MDB_PROVIDER properties for the stores. The first three provider guids have previously been documented in header files as MSPST_UID_PROVIDER, pbExchangeProviderPrimaryUserGuid, and pbExchangeProviderPublicGuid.
[This is now documented here: http://msdn.microsoft.com/en-us/library/bb927656.aspx, http://msdn.microsoft.com/en-us/library/bb905130.aspx, http://msdn.microsoft.com/en-us/library/bb821179.aspx]
(Now that Outlook 2007 is available I'm reposting some of the articles from the Outlook 2007 Beta Documentation series. Some of the articles are virtually unchanged, others are completely different to reflect the differences between Beta and RTM. This article is the updated version of the second half of Outlook 2007 Beta Documentation - Notification Based Indexing Support.)
#define PR_PROVIDER_ITEMID PROP_TAG(PT_BINARY, 0x0EA3) #define PR_PROVIDER_PARENT_ITEMID PROP_TAG(PT_BINARY, 0x0EA4)
#define PR_SEARCH_OWNER_ID PROP_TAG(PT_LONG, 0x3419)
#define PR_ADDITIONAL_REN_ENTRYIDS PROP_TAG(PT_MV_BINARY, 0x36D8)
0 - Conflicts folder 1 - Sync Issues folder 2 - Local Failures folder 3 - Server Failures folder 4 - Junk E-mail Folder
The following properties are used in MAPI restrictions:
#define PR_SEARCH_ATTACHMENTS_W PROP_TAG(PT_UNICODE, 0x0EA5)
#define PR_SEARCH_RECIP_EMAIL_TO_W PROP_TAG(PT_UNICODE, 0x0EA6)
#define PR_SEARCH_RECIP_EMAIL_CC_W PROP_TAG(PT_UNICODE, 0x0EA7)
#define PR_SEARCH_RECIP_EMAIL_BCC_W PROP_TAG(PT_UNICODE, 0x0EA8)
The following named properties are indexed by the MAPI PH. These properties are documented as read-only. They should not be used to create or modify items.
These GUIDs represent the name spaces of the named properties
const GUID PSETID_Appointment = {0x00062002, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; const GUID PSETID_Task = {0x00062003, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; const GUID PSETID_Address = {0x00062004, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; const GUID PSETID_Common = {0x00062008, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; const GUID PSETID_Log = {0x0006200A, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; const GUID PS_PUBLIC_STRINGS = {0x00020329, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; const GUID PS_INTERNET_HEADERS = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
MNID_ID properties:
// In PSETID_Address #define dispidWorkAddressStreet 0x8045 #define dispidWorkAddressCity 0x8046 #define dispidWorkAddressState 0x8047 #define dispidWorkAddressPostalCode 0x8048 #define dispidWorkAddressCountry 0x8049 #define dispidInstMsg 0x8062 #define dispidEmailDisplayName 0x8080 #define dispidEmailOriginalDisplayName 0x8084 // In PSETID_Task #define dispidTaskStartDate 0x8104 #define dispidTaskDueDate 0x8105 #define dispidTaskActualEffort 0x8110 #define dispidTaskEstimatedEffort 0x8111 #define dispidTaskFRecur 0x8126 // In PSETID_Appointment #define dispidLocation 0x8208 #define dispidApptStartWhole 0x820D #define dispidApptEndWhole 0x820E #define dispidApptDuration 0x8213 #define dispidRecurring 0x8223 #define dispidAllAttendeesString 0x8238 #define dispidToAttendeesString 0x823B #define dispidCCAttendeesString 0x823C // In PSETID_Common #define dispidReminderSet 0x8503 #define dispidSmartNoAttach 0x8514 #define dispidCommonStart 0x8516 #define dispidCommonEnd 0x8517 #define dispidRequest 0x8530 #define dispidCompanies 0x8539 #define dispidReminderNextTime 0x8560 // In PSETID_Log (also known as Journal) #define dispidLogType 0x8700 #define dispidLogStart 0x8706 #define dispidLogDuration 0x8707 #define dispidLogEnd 0x8708
MNID_STRING properties
// In PS_PUBLIC_STRINGS "Keywords" // In PS_INTERNET_HEADERS "return-path"
[This is now documented here: http://msdn.microsoft.com/en-us/library/bb821096.aspx, http://msdn.microsoft.com/en-us/library/bb821097.aspx, http://msdn.microsoft.com/en-us/library/bb821099.aspx, http://msdn.microsoft.com/en-us/library/bb821100.aspx]
(Now that Outlook 2007 is available I'm reposting some of the articles from the Outlook 2007 Beta Documentation series. Some of the articles are virtually unchanged, others are completely different to reflect the differences between Beta and RTM. This article is the updated version of the first half of Outlook 2007 Beta Documentation - Notification Based Indexing Support.)
This flag is set by pusher stores.
Definition
#define STORE_PUSHER_OK ((ULONG) 0x00800000)
If the store provider sets this flag the MAPI PH won’t crawl the store and the store is responsible to push any changes through notifications to the indexer to have messages indexed.
Usage
This flag can be retrieved by getting the property PR_STORE_SUPPORT_MASK from the store.
Every time a folder, message or an attachment is to be indexed a unique Unicode URL has to be generated and sent to the indexer. This URL will later be used to identify which object to be indexed in the MAPI PH that runs in the Search Protocol Host. Store providers are responsible for generating these URLs.
MAPI URLs have the following format:
Mapi://SID/StoreDisplayName ($HashNumber)/StoreType/FolderNameA/…/FolderNameN/[EntryIDEncoded[/at=AttachIDEncoded:FileName]]
Parameters
SID
Current user’s SID
HashNumber
DWORD in hex calculated based on the store entry id or the store mapping signature. This value will be stored in the registry and will be used later to identify the store in the MAPI PH.
This number has to be calculated in a way that minimizes collisions between other stores. See below for the algorithm Outlook uses to calculate the hash.
StoreType
Number that identifies the type of the store that contains the object to be indexed. Here are the possible values:
0 – Default store
1 – Delegate store (used for delegate items cached locally)
2 – Public Folders (used for public folder favorites)
Note that if the store is being crawled instead of pushed the value used is none of the above but instead it will be the character X.
FolderNameA/…/FolderNameN
The path from the root of the IPM_SUBTREE to the folder or message. For instance, a message in the “Family” folder under “Inbox” will have Inbox/Family for this parameter.
EntryIDEncoded
MAPI Entry ID for the item encoded as a Unicode string. See below for how it gets encoded. Note that when viewed as text, this encoded entry ID will appear as random Hangul characters or boxes depending on available fonts.
AttachIDEncoded
Attachment ID encoded as a Unicode string.
FileName
Attachment file name as it appears in the message.
Here are some examples of MAPI URLs for a folder, message and attachment respectively:
mapi://S-1-5-21-2127521184-1604012920-1887927527-71418/Mailbox – Some User ($be19928f)/2/Office
mapi://S-1-5-21-2127521184-1604012920-1887927527-71418/Mailbox – Some User ($484efb89)/0/Calendar/곯가가가걍걝걌곌겷걢곒갑겛개가검걟곔걙곾걤곂갠가
mapi://S-1-5-21-2127521184-1604012920-1887927527-71418/Mailbox – Some User ($484efb89)/0/Inbox/곯가가가걍걝걌곌겷걢곒갑겛개가검걟곔걙곾간곷갦가/at=겅걋각가:somefile.txt
Characters that get encoded if they are in the store or folder display name
Here is the table that shows which characters we encode:
% -> %25, / -> %2F \ -> %5C * -> %2A ? -> %3F
Algorithm to calculate the store hash
Note that if a store has PR_MAPPING_SIGNATURE in the global profile section then that’s what we will hash instead of the store entry id. For all other stores we will hash the Entry ID.
DWORD ComputeStoreHash(ULONG cbStoreEID, LPENTRYID pbStoreEID, LPCWSTR pwzFileName) { DWORD dwHash = 0; ULONG cdw = 0; DWORD* pdw = NULL; ULONG cb = 0; BYTE* pb = NULL; ULONG i = 0; // Get the Store Entry ID // pbStoreEID is a pointer to the Entry ID // cbStoreEID is the size in bytes of the Entry ID pdw = (DWORD*)pbStoreEID; cdw = cbStoreEID / sizeof(DWORD); for (i = 0; i < cdw; i++) { dwHash = (dwHash << 5) + dwHash + *pdw++; } pb = (BYTE *)pdw; cb = cbStoreEID % sizeof(DWORD); for (i = 0; i < cb; i++) { dwHash = (dwHash << 5) + dwHash + *pb++; } // You may want to also include the store file name in the hash calculation // Get store FileName // pwzFileName is a NULL terminated string with the path and filename of the store if (pwzFileName) { while (*pwzFileName) { dwHash = (dwHash << 5) + dwHash + *pwzFileName++; } } // dwHash now contains the hash to be used. It should be written in hex when building the URL. return dwHash; }// ComputeStoreHash
Algorithm to encode the Entry ID and the attachment ID
The goal of this algorithm is to generate a compact representation of the Entry ID or attachment ID.
const WORD kwBaseOffset = 0xAC00; // Hangul char range (AC00-D7AF) LPWSTR EncodeID(ULONG cbEID, LPENTRYID rgbID) { ULONG i = 0; LPWSTR pwzDst = NULL; LPBYTE pbSrc = NULL; LPWSTR pwzIDEncoded = NULL; // rgbID is the item Entry ID or the attachment ID // cbID is the size in bytes of rgbID // Allocate memory for pwzIDEncoded pwzIDEncoded = new WCHAR[cbEID]; if (!pwzIDEncoded) return NULL; for (i = 0, pbSrc = (LPBYTE)rgbID, pwzDst = pwzIDEncoded; i < cbEID; i++, pbSrc++, pwzDst++) { *pwzDst = (WCHAR) (*pbSrc + kwBaseOffset); } // Ensure NULL terminated *pwzDst = L'\0'; // pwzIDEncoded now contains the entry ID encoded. return pwzIDEncoded; }// EncodeID
A new MAPI notification type has been introduced to facilitate shutdown scenarios for pusher stores. These stores will have to persist what has to be pushed since it may not be able to index everything before a shutdown occurs. When a store provider is a pusher it should send the following notification so that the MAPI PH knows which process it should watch for a given store. This way if the process is shut down or crashes the MAPI PH will immediately detect that and close (stop indexing) the store it opened.
#define fnevIndexing ((ULONG) 0x00010000) /* Indexing notifications (used for FTE related communications) */ /* Shares EXTENDED_NOTIFICATION to pass structures below, */ /* but NOTIFICATION type will be fnevIndexing */ // Stores that are pusher enabled (PR_SUPPORT_MASK contains STORE_PUSHER_OK) // are required to send notifications regarding the process that is pushing. #define INDEXING_SEARCH_OWNER ((ULONG) 0x00000001) typedef struct _INDEX_SEARCH_PUSHER_PROCESS { DWORD dwPID; /* PID for process pushing */ } INDEX_SEARCH_PUSHER_PROCESS;
When pushing URLs to be indexed pusher stores should also create a blob with some information that will be used by the MAPI PH. This blob will be associated with each URL and will be sent when the URL is pushed to the indexer.
The format of the blob is the following:
DWORD dwVersion DWORD dwFlags ULONG cbProfileName WCHAR wszProfileName ULONG cbProviderItemID WCHAR wszProviderItemID
Note that these values have to be written to the blob in the order shown above. The provider item ID should only be sent for folders to prevent opening extra folders to get this information.
dwVersion
This is the version of the data being sent. Currently this value is 1.
dwFlags
Reserved for future use. Currently this should be 0.
cbProfileName
Size of the profile name in bytes. This information will be useful for the MAPI PH to know which profile to use when indexing the item.
wszProfileName
Null terminated Unicode string with the profile name.
cbProviderItemID
Size of the Provider Item ID in bytes
wszProviderItemID
Null terminated Unicode string with the provider item ID that uniquely identifies the item in the store.
[This is now documented here: http://msdn.microsoft.com/en-us/library/bb820936.aspx]
(Now that Outlook 2007 is available I'm reposting some of the articles from the Outlook 2007 Beta Documentation series. Some of the articles are virtually unchanged, others are completely different to reflect the differences between Beta and RTM. This article is the updated version of Outlook 2007 Beta Documentation - Blocked Attachments.)
Microsoft Outlook includes a feature that blocks attachments that are considered unsafe. The attachments which are blocked can vary from client to client depending on how Outlook is configured and on policies the administrator may have applied. See http://support.microsoft.com/kb/829982 for more information on how this is configured.
Custom code can query to see if a particular attachment is considered blocked by Outlook using the IAttachmentSecurity interface. This interface exposes a function, IsAttachmentBlocked, which will analyze a file name and report if this attachment is considered blocked by Outlook and won’t be shown in the UI or indexed.
DEFINE_GUID(IID_IAttachmentSecurity, 0xB2533636, 0xC3F3, 0x416f, 0xBF, 0x04, 0xAE, 0xFE, 0x41, 0xAB, 0xAA, 0xE2); #define MAPI_IATTACHMENTSECURITY_METHODS(IPURE) \ MAPIMETHOD(IsAttachmentBlocked) \ (LPCWSTR pwszFileName, BOOL *pfBlocked) IPURE; DECLARE_MAPI_INTERFACE_(IAttachmentSecurity, IUnknown) { BEGIN_INTERFACE MAPI_IUNKNOWN_METHODS(PURE) MAPI_IATTACHMENTSECURITY_METHODS(PURE) };
HRESULT IsAttachmentBlocked(LPMAPISESSION lpMAPISession, LPCWSTR pwszFileName, BOOL* pfBlocked) { if (!lpMAPISession || !pwszFileName || !pfBlocked) return MAPI_E_INVALID_PARAMETER; HRESULT hRes = S_OK; IAttachmentSecurity* lpAttachSec = NULL; BOOL bBlocked = false; hRes = lpMAPISession->QueryInterface(IID_IAttachmentSecurity,(void**)&lpAttachSec); if (SUCCEEDED(hRes) && lpAttachSec) { hRes = lpAttachSec->IsAttachmentBlocked(pwszFileName,&bBlocked); } if (lpAttachSec) lpAttachSec->Release(); *pfBlocked = bBlocked; return hRes; }// IsAttachmentBlocked