When is a Deleted Profile Not Deleted?
This post was inspired by a case I worked recently. In this case, the customer was using the 5.5 Event Script service to autoaccept meeting requests. They weren't having any problems with their script or scalability. Their problem was that the service would run fine for days at a time, and then suddenly stop handling all incoming meeting requests. The error they would get in the event logs was this not especially helpful event 11:
Event ID: 11
Source: MSExchangeES
Description: A fatal error (0x80004005)
occurred in an IExchangeEventSink while processing message [Subject = '<subject>']
Examining the script logs was no help - they didn't say anything about the error. This case had me stumped for a while. The latest fixes for events.exe were no good. Turning on internal tracing didn't take me too far either.
Time to pull out the big gun
When I finally got a debugger attached to the events.exe process, I found that OpenMsgStore was returning MAPI_E_CALL_FAILED (==0x80004005). Debugging into that, I found that during handling of OpenMsgStore, MAPI has to go to the registry to retrieve some properties from the profile. MAPI's call to RegQueryValueEx was returning ERROR_KEY_DELETED (==1018). The profile we were using in the Event Script service had been deleted out from under us! Why?
The simplistic answer is that right after MAPILogonEx, events.exe calls DeleteProfile. To understand that, we need to digress.
What does DeleteProfile do?
Well, for starters, it doesn't delete the profile. At least not always. Here's what the MSDN has to say on this function:
The IProfAdmin::DeleteProfile method deletes a profile. If the profile to delete is in use when DeleteProfile is called, DeleteProfile returns S_OK but does not delete the profile immediately. Instead, DeleteProfile marks the profile for deletion and deletes it after it is no longer being used, when all of its active sessions have ended.
Let's break this down:
Check if the profile is in use
MAPI maintains a shared memory object (this is CreateFileMapping, not .Shared sections) for interprocess communication and synchronization. In that shared memory object, we keep a linked list of all profiles currently in use. When we log onto a profile with MAPILogonEx, its ref count is bumped up. When the client logs out, the ref count is dropped back down. So checking if a profile is in use is equivalent to looking in the shared memory object to see if the profile has a ref count.
Mark a profile for deletion
MAPI uses a special reg key to mark a profile for deletion. The key is
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\Deleted Profiles
To mark a profile for deletion, we write the profile name as a subkey. When MAPI is asked to present a list of existing profiles, such as in the MAPILogonEx dialog or in response to GetProfileTable, profiles listed under Deleted Profiles are left out.
Delete a profile after it is no longer being used
This is the hard one. How does MAPI determine the exact instance a profile is no longer in use? During logoff is not sufficient, since we also need to account for cases where the application using MAPI has crashed. So we have to be cleverer than that. We delete profiles from the registry when:
- DeleteProfiles is called and the profile is not currently in use.
- A session is released and the profile is marked for deletion
- MAPIInitialize is called
The first two cases handle most profile deletions. The third case handles the abnormal situations - an application has crashed or leaked a session pointer. The third case is also where bugs in the implementation cause cross process problems.
Here come the bugs, part 1
The first bug is completely our fault. Suppose two different processes run as the same user. Suppose also that one of the processes uses impersonation to run under the credentials of another user. Note that by default, this does not load the impersonated user's registry hive. Doing that requires a different call. (See KB 259301 for example code that does this). So, both processes will use the same registry hive when accessing HKEY_CURRENT_USER.
The first process to start creates a profile for MAPI. It then calls DeleteProfile after logging on so as not to clutter up the registry with temporary profiles. The second process (which has done the impersonation) now comes in and calls MAPIInitialize. We have a problem with our shared memory now - each process got their own block of shared memory. Our scheme now breaks down. The second process checks its shared memory to see what profiles are in use, finds that no profiles are being used, and proceeds to delete every profile listed under "Deleted Profiles". The first process is now what we call hosed.
When this bug was first reported, we weren't sure how to fix it without doing a total rewrite of this feature. That is, until one of our devs had the brilliant insight that each shared memory section was essentially tracking the profiles in use by a particular NT account. So all we needed to do was reflect that in our use of the registry by appending a CRC of the SID under which MAPI is running to the "Deleted Profiles" string. Now all processes which are running MAPI under the same NT account (therefore accessing the same shared memory) are looking at the same place in the registry, and processes running MAPI under different NT accounts look in different places in the registry.
Here come the bugs, part 2
And yet, even after putting in the fix, we still saw profiles being deleted out from under us. This one isn't completely MAPI's fault. Suppose two different processes are running on the same machine. Both run as the same user and neither uses impersonation. However, one runs in Terminal Services and the other runs in the console.
Same dance as the first bug. When the second process goes to create a shared memory object, it does not get the same shared memory as the first one. We don't get the same shared memory because sessions started under Terminal Service use a different namespace than sessions started from the console. In general this is a good thing, but it blocks MAPI from doing what it's trying to do.
The fix from the first bug isn't effective here because the SID for both processes is the same. Fortunately, the fix here is even simpler - it's even spelled out in the MSDN: prepend "Global\\" to the name of the shared memory. Now all processes running as the same user will use the same shared memory regardless of the Terminal Service session.
Where was I?
This started as an Event Script problem, and digressed into a discussion of DeleteProfile. The Event Script service is built around MAPI. It creates a profile on the fly and calls DeleteProfile immediately after logging on. The customer used Terminal Services to manage their server, and to top it off, Outlook was installed on the machine. So every few days, they'd encounter Bug 1 or Bug 2 and their script was toast. I had them remove Outlook, sent them the patch, and they haven't seen the problem since.
Hey Steve, where's this wonderful patch you speak of?
Thought you'd never ask. Here it is by product:
- Exchange 5.5: Bug 1 and 2 were both fixed in the same patch - KB 818861. If MAPI32.dll is 5.5.2657.50 or greater then you've already got the fix.
- Exchange 2000: This is where Bug 1 was originally reported. KB 817375 fixes it. As far as I can tell, no one's reported Bug 2 on Exchange 2000 and the fix for Bug 2 has not yet been ported to Exchange 2000. If you are seeing this problem against Exchange 2000, please open an issue with PSS.
- Exchange 2003: Bug 1 was discovered in time to roll the fix into RTM. Bug 2 was discovered later. No hot fix was issued, but the fix is in SP1.
- Outlook: Neither of these fixes is in any version of Outlook. However, as far as I know, no customer has reported either issue with Outlook. If you are hitting this issue on Outlook machines, you should also open an issue with PSS.
[8/22/04 8:09PM Minor edit - clarified some text]
[8/25/04 11:04AM Clarified impersonation and added links]
Comments
Anonymous
August 20, 2004
.Text editor has no love for me lately - yanked and reformatted.Anonymous
September 14, 2004
So... what does this mean about Outlook on a Terminal Services server?
Thanks,
MAnonymous
September 14, 2004
One requirement to see this problem is an application which creates a profile on the file and then deletes the profile after logging on. There's not many userland applications which do this. In addition to having this app, you'd also have to be logged on to the TS server twice, running Outlook in one session and the app in the other. I would assume this scenario is rather rare.Anonymous
July 19, 2005
There is an article which you used to be able to find at http://support.microsoft.com/kb/259301. This...Anonymous
August 02, 2005
Not sure if this is the right place to post this message, but it seems it may. We recently deployed Outlook 2003 / Exchange 2003 into our environment. Here's the scenerio:
1. My main computer I log on and leave Outlook open.
2. I travel to another building and log onto another machine for the first time and launch Outlook. This will bring up the Startup Wizard rather than use the automated PRF file.
3. If I close out of Outlook on my main machine and then relaunch Outlook on the shared workstation using the PRF it works fine. For some reason it knows that I'm still logged in to my main machine and in Outlook.
This only happens for a user logging into a machine for the first time. I know the OWA is great, but you just can't do everything you need to do. :)
Here's the top portion of the PRF we're using. We are also created a Personal Folder (PST) during the logon process to save in the users home directory H: drive
========================================
PRF
[Service List]
ServiceX=Microsoft Outlook Client
ServiceEGS=Exchange Global Section
Service3=Personal Folders
Service2=Outlook Address Book
Service1=Microsoft Exchange Server
ServiceEGS=Exchange Global Section
Thanks
Dan
danjphillips@gmail.comAnonymous
August 08, 2005
Dan - I guess I don't see what this has to do with my article on deleted profiles. I don't have much experience with PRF files themselves. Perhaps you should try the newsgroups?Anonymous
August 08, 2005
There is an article which you used to be able to find at http://support.microsoft.com/kb/259301. This...Anonymous
April 08, 2006
In this article I will explain how the OAB Generation process discovers the domain controller that it...Anonymous
April 10, 2006
I have this problem if i have only MS Outlook ( 2003 SP1) in my system and if i try to create temporary profiles in a m/c with both MS Outlook and exchange ( both 2003 SP1) then temporary profiles are not deleted.
Do you have any comment for me .... :-(Anonymous
April 12, 2006
Gokul - I'm not sure I understand your question. As I noted, the changes we made to Exchange's MAPI to work around this problem were not made to Outlook's MAPI. Sorry.Anonymous
May 11, 2006
I run a MAPI program which creates a profile, logs on to get session and deletes the profile to do some calendaring operation for a impersonated user. It's fine.
But when I run the same program wrapped by Java in Web server (JNI), then It works fine for the first time, for the next time I do the operation, I get a error in Create Profile.This is because the previous time, the profile is not deleted.
My conclusion is that there is a problem when I do the operations in the same process (java.exe). Do you have an explanation for this?
My Operations are - windowslogon, impersonate, mapiintialize, createprofile,logonex,deleteprofile.....mapisession.logoff, mapisession.release,mapiUninitialize,revertoself. These operation repeat when the user logs in again in the same process.
I run this on Exchange 2003 System Tools (I am yet to try the SP1 fix)Anonymous
May 11, 2006
Prabhu - I know Java doesn't clean stuff up immediately - it relies on garbage collection. It sounds to me like all your objects haven't been cleaned up. As long as the session is still active (as it will be while objects are waiting for GC) the profile won't be deleted.
The simple workaround here is to always use a unique profile name. Assuming GC eventually takes care of the open objects, the profiles will get deleted.Anonymous
May 18, 2006
Stephen - Why should deleting a profile should rely on Java's GC? I have released the session that was marked for use. This satisfies your second case for deleting the profile.
Further to confirm this I called the same function (that peforms the operations I mentioned earlier) twice from my C program itself. I have the same problem creating the profile for the second time the function is called.
I cannot take your workaround because for each user/operation I will be creating profile this is a big number for a server side application and we can't leave them undeleted until the server shutdown.Anonymous
May 19, 2006
Prabhu,
This isn't the best forum to investigate what's wrong with your code. You can post your questions on the MAPI newsgroup, microsoft.public.platformsdk.mapi or the MAPI mailing list, http://peach.ease.lsoft.com/archives/mapi-l.html.
SteveAnonymous
May 30, 2007
This was discussed a while back in one of the newsgroups . I figured I'd document it a little more permanentlyAnonymous
August 22, 2007
There is an article which you used to be able to find at http://support.microsoft.com/kb/259301. ThisAnonymous
June 02, 2008
The MAPI/CDO download package now works on Windows Server 2008 and Vista. The main blocker to getting