InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Determining the type of an user account

When playing around with Microsoft Exchange security descriptors you inevitably come to the point where you must sort the security descriptors according to the following rules:

    1 REPEAT <n>



    4 REPEAT <m>



    7 REPEAT <m>



   10 REPEAT <0 or 1>


(This was taken from the article Exchange 5.5 Access Rights and the Exchange Store on MSDN)

So you must somehow distinguish between normal users, groups, the everyone group and the anonymous group. If you read the security descriptor as XML stream via the property, you get this information for the existing entries. But if you add another principal to the list, you must insert the entry at the correct position, depending of the type of the account being added.

Unfortunately, you can get this information from the Security identifier of the entry only for a few exceptions: The Everyone group and the anonymous groups (see this KB article). To identify the all others, you have basically two options:

  • Get the corresponding entry from Active Directory and check the object class
  • Use Win32 functions LookupAccountSid and LookupAccountName, which return the type of the account

In this article, I'll present a .NET implementation for the latter approach.

The implementation

The following code is taken from my InfiniTec.Security library, and implements a class, which takes a IdentityReference instance in its constructor. The account type is then exposed via the AccountType property. Note that you can also specify a remote computer, on which the translation should be performed. Since no alternative credentials can be specified, you must impersonate another user account if needed.

    1 using System;

    2 using System.Runtime.InteropServices;

    3 using System.Security.Principal;

    4 using System.Text;


    6 ///<summary>

    7 /// Provides the ability to translate a <see cref="NTAccount"/> to a <see cref="SecurityIdentifier"/>

    8 /// and vice versa, optionally using a remote computer for the translation process.

    9 ///</summary>

   10 publicclassIdentityResolver: IEquatable<IdentityReference>

   11 {

   12     privatestring _ComputerName;

   13     privateAccountType _AccountType;

   14     privateNTAccount _Account;

   15     privateSecurityIdentifier _Sid;


   17     ///<summary>

   18     /// Returns the <see cref="SecurityIdentifier"/> of this instance.

   19     ///</summary>

   20     publicSecurityIdentifier Sid

   21     {

   22         get

   23         {

   24             if (_Sid == null) TranslateFromNTAccount();

   25             return _Sid;

   26         }

   27     }

   28     ///<summary>

   29     /// Returns the <see cref="NTAccount"/> of this instance.

   30     ///</summary>

   31     publicNTAccount Account

   32     {

   33         get

   34         {

   35             if (_Account == null) TranslateFromSecurityDescriptor();

   36             return _Account;

   37         }

   38     }


   40     ///<summary>

   41     /// Returns the <see cref="AccountType"/> of this instance

   42     ///</summary>

   43     publicAccountType AccountType

   44     {

   45         get {

   46             if (_Sid == null) TranslateFromNTAccount();

   47             elseif (_Account == null) TranslateFromSecurityDescriptor();


   49             return _AccountType;

   50         }

   51     }


   53     ///<summary>

   54     /// Creates a new instance of this class. A remote computer is not used for the

   55     /// translation process

   56     ///</summary>

   57     ///<param name="identity">The identity to translate</param>

   58     public IdentityResolver(IdentityReference identity): this(identity, null) { }


   60     ///<summary>

   61     /// Creates a new instance of this class.

   62     ///</summary>

   63     ///<param name="identity">The identity to translate</param>

   64     ///<param name="computerName">The computer to use for the translation</param>

   65     ///<remarks>The remote computer is not used for translation, if

   66     /// the provided identity is a <see cref="SecurityIdentifier"/> and the domain

   67     /// sid of that identity equals the domain sid of the current user</remarks>

   68     public IdentityResolver(IdentityReference identity, string computerName)

   69     {

   70         _Sid = identity asSecurityIdentifier;

   71         _Account = identity asNTAccount;


   73         if (_Sid == null || !_Sid.IsEqualDomainSid(WindowsIdentity.GetCurrent().User))

   74         {

   75             _ComputerName = computerName;

   76         }


   78     }


   80     privatevoid TranslateFromNTAccount()

   81     {

   82         byte[] binarySid;

   83         uint binarySidLength;

   84         StringBuilder referencedDomain;

   85         uint referencedDomainLength;

   86         bool result;


   88         binarySid = newbyte[SecurityIdentifier.MaxBinaryLength];

   89         binarySidLength = (uint) binarySid.Length;

   90         referencedDomain = newStringBuilder(0xff);

   91         referencedDomainLength = (uint) referencedDomain.Capacity;


   93         result = NativeMethods.LookupAccountName(_ComputerName, _Account.Value,

   94             binarySid, ref binarySidLength, referencedDomain,

   95             ref referencedDomainLength, out _AccountType);


   97         if (!result) throwMarshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());


   99         _Sid = newSecurityIdentifier(binarySid, 0);


  101     }


  103     privatevoid TranslateFromSecurityDescriptor()

  104     {

  105         byte[] binarySid;

  106         StringBuilder name;

  107         StringBuilder domain;

  108         uint nameLength;

  109         uint domainLength;

  110         bool result;


  112         name = newStringBuilder(0x100);

  113         domain = newStringBuilder(0xff);


  115         nameLength = (uint) name.Capacity - 2;

  116         domainLength = (uint) domain.Capacity - 2;


  118         binarySid = newbyte[_Sid.BinaryLength];

  119         _Sid.GetBinaryForm(binarySid, 0);


  121         result = NativeMethods.LookupAccountSid(

  122             _ComputerName, binarySid, name, ref nameLength, domain, ref domainLength, out _AccountType);


  124         if (!result) throwMarshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());


  126         _Account = newNTAccount(domain.ToString(), name.ToString());

  127     }


  129     #region IEquatable<NTAccount> Members


  131     ///<summary>

  132     /// Compares this identity with the specified othe identity.

  133     ///</summary>

  134     ///<param name="other">The identity to compare this identity with.</param>

  135     ///<returns>True, if both identities represent the same identity</returns>

  136     publicbool Equals(IdentityReference other)

  137     {

  138         if (other == null) returnfalse;


  140         if (other isNTAccount) return Account.Equals(other);

  141         elseif (other isSecurityIdentifier) Sid.Equals((SecurityIdentifier) other);


  143         returnfalse;

  144     }


  146     #endregion

  147 }

The interesting stuff happens in the TranslateFromSecurityDescriptor and the TranslateFromNTAccount method. They prepare some buffers and call the LookupAccountSid and LookupAccountName function. On return of either these functions, the account type is stored in the _AccountType field.

The various account types

The example code above exposes the account type as an enumeration. Here is the implementation of this enumeration:

    1 ///<summary>

    2 /// Defines the various account types of a Windows accunt

    3 ///</summary>

    4 publicenumAccountType

    5 {

    6     ///<summary>

    7     /// No account type

    8     ///</summary>

    9     None = 0,

   10     ///<summary>

   11     /// The account is a user

   12     ///</summary>

   13     User,

   14     ///<summary>

   15     /// The account is a security group

   16     ///</summary>

   17     Group,

   18     ///<summary>

   19     /// The account defines a domain

   20     ///</summary>

   21     Domain,

   22     ///<summary>

   23     /// The account is an alias

   24     ///</summary>

   25     Alias,

   26     ///<summary>

   27     /// The account is a well-known group, such as BUILTIN\Administrators

   28     ///</summary>

   29     WellknownGroup,

   30     ///<summary>

   31     /// The account was deleted

   32     ///</summary>

   33     DeletedAccount,

   34     ///<summary>

   35     /// The account is invalid

   36     ///</summary>

   37     Invalid,

   38     ///<summary>

   39     /// The type of the account is unknown

   40     ///</summary>

   41     Unknown,

   42     ///<summary>

   43     /// The account is a computer account

   44     ///</summary>

   45     Computer

   46 }

Interop methods

To use the Win32 functions from .NET, we must implement a class with the interop definitions for the two methods:

    1 staticclassNativeMethods

    2 {

    3     [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]

    4     [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]

    5     [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]

    6     [return: MarshalAs(UnmanagedType.Bool)]

    7     publicstaticexternbool LookupAccountSid(

    8         [In] string systemName,

    9         [In, MarshalAs(UnmanagedType.LPArray)] byte[] sid,

   10         [Out] StringBuilder name,

   11         [In, Out] refuint nameLength,

   12         [Out] StringBuilder referencedDomainName,

   13         [In, Out] refuint referencedDomainNameLength,

   14         [Out] outAccountType usage);


   16     [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]

   17     [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]

   18     [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]

   19     [return: MarshalAs(UnmanagedType.Bool)]

   20     publicstaticexternbool LookupAccountName(

   21         [In] string systemName,

   22         [In] string accountName,

   23         [Out, MarshalAs(UnmanagedType.LPArray)] byte[] sid,

   24         [In, Out] refuint sidSize,

   25         [Out] StringBuilder referencedDomainName,

   26         [In, Out] refuint referencedDomainNameLength,

   27         [Out] outAccountType accountType);

   28 }

This class allows us to call the methods from .NET


Posted by Henning Krause on Monday, March 12, 2007 12:00 AM, last modified on Monday, March 12, 2007 8:00 PM
Permalink | Post RSSRSS comment feed

Decoding entry ids

The EntryId (stored in the property is a binary structure uniquely identifying elements in the Exchange store. It basically consists of two identifiers (three for folders): The MAPI store where the element is located (either a mailbox or a public folder tree), the folder identifier and the message identifier. Unfortunately, the store identifier cannot be mapped to a mailbox or public folder tree, so you must know in which store the element is located. But this should not be an issue in most cases. The folder and item identifiers consists of a global unique identifier and a sequential number. The combined value of these identifiers can be used to construct a permanent url (found in the property

A permanent url looks like this: http://server/exchange/user/-FlatUrlSpace-/8996592620a48a4393901c9368f7c518-1488/8996592620a48a4393901c9368f7c518-1af3

This url never changes unless the item is moved to another folder.

Below is method which constructs such a permanent url based on the url to the store (the http://server/exchange/user in the above example) and an EntryId structure:

    1 using System;

    2 using System.Globalization;

    3 using System.IO;


    5 namespace InfiniTec.Exchange

    6 {

    7     staticclassExchangeStoreReference

    8     {

    9         publicstaticstring DecodeEntryId(BinaryReader reader, string baseUrl)

   10         {

   11             Guid folderId;

   12             ulong folderCnt;


   14             // First reserved field

   15             reader.ReadUInt32();


   17             // Now comes the store guid.

   18             reader.ReadBytes(16);


   20             // Next reserved field

   21             reader.ReadUInt16();


   23             folderId = ReadGuid(reader);

   24             folderCnt = SwapUInt64(reader.ReadUInt64());


   26             if (!baseUrl.EndsWith("/"))

   27             {

   28                 baseUrl += "/";

   29             }


   31             if (reader.BaseStream.Length - reader.BaseStream.Position >= 24)

   32             {

   33                 Guid messageId;

   34                 messageId = ReadGuid(reader);

   35                 ulong messageCnt;

   36                 messageCnt = SwapUInt64(reader.ReadUInt64());

   37                 baseUrl += string.Format(CultureInfo.CurrentCulture, baseUrl + "/-FlatUrlSpace-/{0:N}-{1:x}/{2:N}-{3:x}", folderId, folderCnt, messageId, messageCnt);

   38             }

   39             else

   40             {

   41                 baseUrl += string.Format(CultureInfo.CurrentCulture, baseUrl + "/-FlatUrlSpace-/{0:N}-{1:x}", folderId, folderCnt);

   42             }

   43             return baseUrl;


   45         }


   47         privatestaticGuid ReadGuid(BinaryReader reader)

   48         {

   49             int a;

   50             short b, c;


   52             a = SwapInt(reader.ReadUInt32());

   53             b = reader.ReadInt16();

   54             c = SwapShort(reader.ReadUInt16());

   55             returnnewGuid(a, b, c, reader.ReadBytes(8));

   56         }


   58         privatestaticshort SwapShort(ushort value)

   59         {

   60             unchecked

   61             {

   62                 ushort result;

   63                 result = (ushort)​(((value & 0xFF00) >> 8) |

   64                                ((value & 0x00FF) << 8));


   66                 return (short)result;

   67             }

   68         }


   70         privatestaticint SwapInt(uint value)

   71         {

   72             uint result;


   74             result = ((value & 0xFF000000) >> 24) |

   75                     ((value & 0x00FF0000) >> 8) |

   76                     ((value & 0x0000FF00) << 8) |

   77                     ((value & 0x000000FF) << 24);


   79             unchecked

   80             {

   81                 return (int)result;

   82             }

   83         }



   86         privatestaticulong SwapUInt64(ulong value)

   87         {

   88             uint lo;

   89             uint hi;

   90             ulong result;


   92             lo = (uint)​(value & 0xffffffff);

   93             hi = ((uint)​(value >> 32)) & 0xffffffff;


   95             lo = ((lo & 0xFF000000) >> 8) |

   96                 ((lo & 0x00FF0000) << 8) |

   97                 ((lo & 0x0000FF00) >> 8) |

   98                 ((lo & 0x000000FF) << 8);


  100             hi = ((hi & 0xFF000000) >> 8) |

  101                 ((hi & 0x00FF0000) << 8) |

  102                 ((hi & 0x0000FF00) >> 8) |

  103                 ((hi & 0x000000FF) << 8);


  105             result = (((ulong)lo) << 32) | hi;


  107             return result;

  108         }

  109     }

  110 }

As you can see, there is some bit-flipping going on during the process. This is because the guids in the EntryId are stored differently than they are used in the permanent url. At last, the folder id, folder sequence number, element id and element sequence number are concatenated and appended to the base url. The result is a permanent url which can be used to access the element.

To use this method, just wrap the binary value of the EntryId in a BinaryReader class and pass that instance to the DecodeEntryId method.


  • The method above will not work for all EntryIds. Depending on how the EntryId was obtained, it may have a different structure. This is because EntryIds can either be short-term or long-term identifiers and are usually just passed to the IMsgStore::OpenEntry method within a MAPI session. But an EntryId obtained through the property should work.
  • If you obtained the EntryId via a WebDAV PROPFIND command, you must decode the BASE64 string to a binary array using the Convert.FromBase64String() method.


Posted by Henning Krause on Saturday, March 10, 2007 12:00 AM, last modified on Thursday, March 8, 2007 9:00 PM
Permalink | Post RSSRSS comment feed

How to use applications settings from an event sink

A while ago, I posted an article on how to create managed event sinks. This is a follow-up on that article covering application settings.

Normally, application files are named after the .EXE file which hosts a managed component: An .exe file called myapplication.exe has a configuration file of myapplication.exe.config which resides in the same directory as the myapplication.exe file.

There are two issues when dealing with Exchange event sinks: First, the working directory is %systemroot%\system32. And second, the executable host is svchost.exe. With .NET 1.1, the only option here was to create a svchost.exe.config in %systemroot%\system32. Bad days for those with more than one managed COM+ application on one server.

Since .NET 2.0, the world has become a better place. At least, in this scenario. You can now place your configuration data in a file named application.config and place that file into the folder where your COM+ application resides. You must then create an xml file in that directory with the name application.manifest and the following content:

    1 <?xmlversion="1.0"encoding="UTF-8"standalone="yes"?>

    2 <assemblyxmlns="urn:schemas-microsoft-com:asm.v1"manifestVersion="1.0" />

Now, you must reconfigure your COM+ application to set the working directory to the directory where your COM+ DLLs reside: Open Component Services, navigate to your applicatoin and open the properties.

Activation propertysheet in component services (click to enlarge)
In the text box Application Root Directory, specify the path your event sink.

That's all.


Posted by Henning Krause on Wednesday, January 31, 2007 12:00 AM, last modified on Wednesday, January 31, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

How to read the Internet Message Header from an email via WebDAV

Ever wanted to read the internet message header of an email via WebDAV?

Internet message headers in Outlook (click to enlarge)

This property is available via the property


Posted by Henning Krause on Monday, October 2, 2006 12:00 AM, last modified on Sunday, October 29, 2006 11:00 AM
Permalink | Post RSSRSS comment feed

Formbased authentication and Exchange 2007

If you have tried to access an Exchange 2007 mailbox or public folder via WebDAV with Formbased Authentication enabled, you might have found out, that it does not work in the current bet: Although you get the necessary cookies, the next request fails with a 440-Login Timeout.

This happens because of the way the Exchange virtual directories are set up - the /Exchange virtual directory runs in another application pool than the /exchweb virtual. This results in different encryption keys being used for the encoding of the cookies.

To work around this, ensure that /OWA and the /Exchange and /Public virtual folders are running in the same application domain.

However, these changes are unsupported and may break uninstallation of Exchange 2007.

This issue has been fixed in post-beta 2 releases.

Btw, the Exchange version I have tested this on is 8.0.605.16.


Posted by Henning Krause on Thursday, September 21, 2006 12:00 AM, last modified on Monday, November 29, 2010 8:59 PM
Permalink | Post RSSRSS comment feed

Finding the Junk Email folder

Ever wondered how to get hold of the Junk Email folder? Since it is localized, one can't simply use /exchange/username/Junk E-Mail. The Exchange SDK contains an article on how to get the urls of the wellknown folders like inbox, tasks, calendar folder, etc. (See Exchange 2003 and Exchange 2007). But no sign of the junk email folder. Luckily, that list is simply incomplete; you can get the junk email folder by reading the property urn:schemas:httpmail:junkemail...


Posted by Henning Krause on Wednesday, September 13, 2006 12:00 AM, last modified on Wednesday, September 13, 2006 12:00 PM
Permalink | Post RSSRSS comment feed

Why we need &lt; and &gt; instead of < and >

A question which came up lately is why we need to escape < and > characters when issuing a SEARCH request on an Exchange folder...

In fact, this has nothing whatsoever to do with Exchange or the WebDAV protocol - at least, not directly. It just happens that the WebDAV protocol uses XML as the transport medium, and in XML, those characers are reserved ones; After all, they are used to start and end tags...


Posted by Henning Krause on Wednesday, August 30, 2006 12:00 AM, last modified on Wednesday, August 30, 2006 12:00 PM
Permalink | Post RSSRSS comment feed

Reading the Spam Confidence Level from an email via WebDAV


The Spam Confidence level is stored by the Intelligent Message filter on every message. It is stored in a MAPI property called PR_SCL. The value of this property is 0x40760003.

Via WebDAV, this property is available via the property


Posted by Henning Krause on Friday, July 7, 2006 12:00 AM, last modified on Friday, July 7, 2006 12:00 PM
Permalink | Post RSSRSS comment feed

Building managed event sinks


  • 2008-04-11
    • Added information for EventSinks on Windows 2000
  • 2007-09-13
    • Revised the last section and clarified necessary permissions to run the event sink
    • Added links to related articles

More Information

On MSDN , there is an article describing describes the necessary steps to create an event sink with managed code (Building Managed Event Sink DLLs), and another one (Managing Event Registrations) explains how to register the eventsink on the target server. Unfortunately, that article does not explain how to deploy the event sink into production. The part where the COM+ application is created is simply missing. Additionally, the example misses one attribute that should be added to the assemblyinfo.cs (or assemblyinfo.vb).

The necessary attributes

Apart from the AssemblyKeyName or AssemblyKeyFileAttribute, you should add these attributes to the assemblyinfo.* file:

    1 [assembly: ApplicationActivation(ActivationOption.Server)]

    2 [assembly: ApplicationName("Title of COM+ Application")]

    3 [assembly: ApplicationAccessControl(false ,AccessChecksLevel = AccessChecksLevelOption.Application)]

Be sure to insert the correct name of the COM+ application in line 2.

Installing the COM+ application on a production server

Once you've copied all the assemblies into a folder on the destination server, you can use the Regvcs tool to register the eventsink assembly and create the COM+ application (Note: The above link points to the .NET 2.0 documentation. But the tool is also available for .NET 1.x). 

Just call

    1 regsvcs -fc managedeventsink.dll

The only thing left to do is to set the process identity for the newly created COM+ application. To do this, open the Component Services MMC (Under administrative tools) and navigate to your COM+ application. Open the properties of the application and select the identity tab. This looks like this:

Here you should select "This user" and enter a user account which has access to the folder to which the event sink will be bound. If you are writing a global event sink which is bound to a mailbox store, and want to access items outside of the scope from the thread on which the OnSyncSave method is called, that account must have permissions to access all mailboxes on that store. See How to grant access to all mailboxes on a mailbox store to a special account for more information on how to do this.

The event sink can now be registered on the Exchange server as described in the second article mentioned at the beginning of this article (See this article if you want to install the sink on Exchange 2007 64bit)

If you want to use an application settings file, you shoud read this article.

Considerations for Windows 2000

If you need to register your event sink on Windows 2000 you will run in a few problems because the COM+ version available. There are two main issues on this platform:

  • Loading of dependent assemblies - Windows 2000 will look for dependent assemblies of the eventsink in the working directory - which is %SystemRoot%\system32. To get around this issue, install all assemblies in the Global Assembly Cache.
  • Accessing configuration data - On Windows 2003 you can access an application.config by creating a manifest file and setting some COM+ options (See How to use applications settings from an event sink). Unfortunately, the required options are not available on Windows 2000. You'll have to put your configuration settings into the file %SystemRoot%\System32\dllhost.exe.config. The file will most likely not exists, so you'll have to create it.

Posted by Henning Krause on Thursday, April 13, 2006 12:00 AM, last modified on Friday, April 11, 2008 12:00 PM
Permalink | Post RSSRSS comment feed

Iterating through all mailboxes in an Exchange 2000/2003 organization

Description [Updated]


  • 2008-07-23: Corrected intra-site links.

One question that came up lately in the newsgroups is: How can I find messages containing certain keywords in the mailboxes of all users in my organization?

This scenario is not directly supported by Exchange. All one can do is to search each mailbox individually.

You must follow these steps to do the search over all mailboxes:

  1. Enumerate the users which have a mailbox. Essentially, these are the users appearing on the global address list. See How to get the Global Address List programatically for more information on how to do this.
  2. Build the mailbox url which can be used to access the mailbox via WebDAV or ExOleDB. See Get the WebDAV url for an Exchange 2000/2003 mailbox on how to do this. If you are using the ExOleDB provider or want to use the administrative virtual root instead, see the remarks section for more information.
  3. Once you have the url for the mailbox you can start accessing it. If you must support different languages, see Getting Well-Known Mailbox Folder URLs on MSDN to get the url of the default folders.
  4. If you are using WebDAV and have Form-based-authentication enabled on your server, you must do a manual logon to the mailbox. See Access the Exchange store via WebDAV with Form-Based-Authentication turned on.



Depending on how you want to access the mailboxes, you need different permissions:

  • If you are using the normal urls (e.g. http://myserver/exchange/username), you need access permissions to all mailboxes on the MAPI level. See HOWTO: Grant access to all mailboxes on a mailbox store to a special account on how to do this. If you have more than one mailbox store, you should grant the necessary permissions on each mailbox store. To simplify this process, you could also grant the "Send as" and "Receive as" permission on the Administrative Groups container via ADSIEdit.msc instead of each mailbox store.
  • You can also use the administrative virtual root. This method is used by the Exchange Systems manager, and it is available via WebDAV and ExOleDB. The normal MAPI permissions are completely ignored when using this method, but an administrative account is required to use this method (See Working with Store Permissions in Microsoft Exchange 2000 and 2003 on Technet for more information on this topic).

Mailbox urls

  • If you are using WebDAV to access the store, you can simply build the mailbox url based on the article Get the WebDAV url for an Exchange 2000/2003 mailbox. To use the administrative root instead, modify the url from http://myserver/exchange/mailboxname to http://myserver/exadmin/admin/<dsndomainname>/mbx/<mailboxname>. You must replace the <dnsdomainname> with the primary smtp domain name of your organization.
  • If you are using ExOleDB, you must modify the address from http://myserver/exchange/mailboxname to file://./backofficestorage/<dnsdomainname>/mbx/<mailboxname>. To use the administrative virtual root, change this url to file://./backofficestorage/admin/<dnsdomainname>/mbx/<mailboxname>.

Posted by Henning Krause on Thursday, April 13, 2006 12:00 AM, last modified on Wednesday, July 23, 2008 10:59 PM
Permalink | Post RSSRSS comment feed