Search code examples
c#.netoutlookactive-directorydistribution-list

Error adding/removing Outlook Distribution List users through .NET


I am a co-owner of several Outlook Distribution Lists (DL's). I can edit them in Outlook, adding and removing members directly in there. However, I cannot edit them through a simple .NET program:

using System;
using System.DirectoryServices.AccountManagement;

namespace DL_Remove_User
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                RemoveUser("My Distribution List", "jimtut");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.ToString());
            }
        }

        private static void RemoveUser(string dl, string username)
        {
            using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "CORP"))
            {
                GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, dl);
                bool result = group.Members.Remove(pc, IdentityType.SamAccountName, username);
                Console.WriteLine(result.ToString());
                group.Save();
            }
        }
    }
}

This same code works on many other DL's, but for a couple, I get the message "Access is Denied". Full stack trace:

at System.DirectoryServices.Interop.UnsafeNativeMethods.IAds.SetInfo()

at System.DirectoryServices.DirectoryEntry.CommitChanges()

at System.DirectoryServices.AccountManagement.ADStoreCtx.UpdateGroupMembership(Principal group, DirectoryEntry de, NetCred credentials, AuthenticationTypes authTypes)

at System.DirectoryServices.AccountManagement.SDSUtils.ApplyChangesToDirectory(Principal p, StoreCtx storeCtx, GroupMembershipUpdater updateGroupMembership, NetCred credentials, AuthenticationTypes authTypes)

at System.DirectoryServices.AccountManagement.ADStoreCtx.Update(Principal p)

at System.DirectoryServices.AccountManagement.Principal.Save()

at Department_Distribution_Lists.Program.RemoveUser(String dl, String username) in Program.cs:line 483

Of course, "Access is denied" does indicate a permission problem, but I can edit these DL's directly in Outlook. I can even query the DL "owners" in AD/LDAP, and I'm in the collection "msExchCoManagedByLink".

Any thoughts on why I can edit in Outlook but not through .NET?


Solution

  • I finally figured this out. I was confused by this permissions problem since I could edit the DL in Outlook, but not thru .NET.

    I started looking for differences between the DL's that I could edit thru .NET and those that I could not, and found the difference was represented in the AD property shown in this GUI as "Manager can update membership list":

    dl

    Even though I was the "manager" (list owner), if the DL didn't have that property set, I could ONLY edit in Outlook.

    I didn't want to have to visually check all the DL's, so I wrote the following code to detect the "real" owners/editors of a DL:

        static List<string> GetGroupOwners(GroupPrincipal group)
        {
            List<string> owners = new List<string>();
            DirectoryEntry deGroup = group.GetUnderlyingObject() as DirectoryEntry;
            ActiveDirectorySecurity ads = deGroup.ObjectSecurity;
            AuthorizationRuleCollection rules = ads.GetAccessRules(true, true, typeof(SecurityIdentifier));
            Guid exRight_Member = new Guid("{bf9679c0-0de6-11d0-a285-00aa003049e2}");
    
            foreach (ActiveDirectoryAccessRule ar in rules)
            {
                if (ar.ActiveDirectoryRights.HasFlag(ActiveDirectoryRights.GenericWrite) || (ar.ObjectType.Equals(exRight_Member) && ar.ActiveDirectoryRights.HasFlag(ActiveDirectoryRights.WriteProperty)))
                {
                    string friendlyName = "";
                    try
                    {
                        friendlyName = ar.IdentityReference.Translate(typeof(NTAccount)).Value;
                    }
                    catch
                    {
                    }
                    owners.Add(friendlyName);
                }
            }
            return owners;
        }
    

    If you want to know who has Outlook-based edit access, that's different:

        static List<string> GetGroupOwnersOutlook(GroupPrincipal group)
        {
            List<string> owners = new List<string>();
            DirectoryEntry deGroup = group.GetUnderlyingObject() as DirectoryEntry;
            System.DirectoryServices.PropertyCollection r = deGroup.Properties;
            foreach (string a in r["managedBy"])
            {
                owners.Add(a);
            }
            foreach (string a in r["msExchCoManagedByLink"])
            {
                owners.Add(a);
            }
    
            return owners;
        }