I spend more than a day to find out that the Principal object is using way more bandwidth than the using the DirectoryServices. The scenario is like this. I have a group with ~3000 computer objects in it. To check if a computer is in this group I retrieved the GroupPrincipal and searched for the ComputerPrincipal.
Boolean _retVal = false;
PrincipalContext _principalContext = null;
using (_principalContext = new PrincipalContext(ContextType.Domain, domainController, srv_user, srv_password)) {
ComputerPrincipal _computer = ComputerPrincipal.FindByIdentity(_principalContext, accountName);
GroupPrincipal _grp = GroupPrincipal.FindByIdentity(_principalContext, groupName);
if (_computer != null && _grp != null) {
// get the members
PrincipalSearchResult<Principal> _allGrps = _grp.GetMembers(false);
if (_allGrps.Contains(_computer)) {
_retVal = true;
}
else {
_retVal = false;
}
}
}
return _retVal;
Actually very nice interface but this creates around 12MB traffic per request. If you're domain controller is in the LAN this is no issue. If you access the domain controller using the WAN it kills your connection/application.
After I noticed this I re-implemented the same functionality using DirectoryServices
Boolean _retVal = false;
DirectoryContext _ctx = null;
try {
_ctx = new DirectoryContext(DirectoryContextType.DirectoryServer, domainController, srv_user, srv_password);
} catch (Exception ex) {
// do something useful
}
if (_ctx != null) {
try {
using (DomainController _dc = DomainController.GetDomainController(_ctx)) {
using (DirectorySearcher _search = _dc.GetDirectorySearcher()) {
String _groupToSearchFor = String.Format("CN={0},", groupName);
_search.PropertiesToLoad.Clear();
_search.PropertiesToLoad.Add("memberOf");
_search.Filter = String.Format("(&(objectCategory=computer)(name={0}))", accountName); ;
SearchResult _one = null;
_one = _search.FindOne();
if (_one != null) {
int _count = _one.Properties["memberOf"].Count;
for (int i = 0; i < _count; i++) {
string _m = (_one.Properties["memberOf"][i] as string);
if (_m.Contains(groupName)) { _retVal = true; }
}
}
}
}
} catch (Exception ex) {
// do something useful
}
}
return _retVal;
This implementation will use around 12K of network traffic. Which is maybe not as nice but saves a lot of bandwith.
My questions is now if somebody has an idea what the AccountManagement object is doing that it uses so much bandwith?
THANKS!
I would guess including the following lines will do a lot to save bandwidth:
_search.PropertiesToLoad.Clear();
_search.PropertiesToLoad.Add("memberOf");
_search.Filter = String.Format("(&(objectCategory=computer)(name={0}))", accountName);
The first two are telling the DirectorySearcher to only load a single property instead of who knows how many, of arbitrary size.
The second is passing a filter into the DirectorySearcher, which I guess is probably processed server side, further limiting the size of your result set.
Related
I'm trying to create a folder under my Inbox in Office 365 using MS Graph 2.0, but I'm finding surprisingly little information on the topic anywhere on the internet. The authentication works fine, and I was able to read the existing test folder. My method for doing this is below:
private void SetupMailBoxes()
{
SmartLog.EnterMethod("SetupMailBoxes()");
MailFolder inbox = null;
try
{
bool dbErrorFolder = false;
bool exchangeErrorFolder = false;
inbox = _journalMailbox.MailFolders.Inbox.Request().GetAsync().GetAwaiter().GetResult();
if (inbox.ChildFolderCount > 0)
{
inbox.ChildFolders = _journalMailbox.MailFolders.Inbox.ChildFolders.Request().GetAsync().GetAwaiter().GetResult();
}
if (inbox.ChildFolders != null)
{
for (int i = 0; i < inbox.ChildFolders.Count && (!dbErrorFolder || !exchangeErrorFolder); i++)
{
if (inbox.ChildFolders[i].DisplayName.ToLower() == "db-error-items")
{
dbErrorFolder = true;
}
else if (inbox.ChildFolders[i].DisplayName.ToLower() == "exchange-error-items")
{
exchangeErrorFolder = true;
}
}
}
if (!dbErrorFolder)
{
try
{
//inbox.ODataType = "post";
var folder = _journalMailbox.MailFolders.Inbox.Request().CreateAsync(
new MailFolder()
{
DisplayName = "DB-Error_Items",
}).GetAwaiter().GetResult();
//inbox.ChildFolders.Add(folder);
}
catch (Exception ex)
{
throw;
}
}
}
catch (Exception exp)
{
SmartLog.LeaveMethod("SetupMailBoxes()");
throw;
}
finally
{
}
SmartLog.LeaveMethod("SetupMailBoxes()");
}
Where _clientSecretCredential is created like this:
_graphServiceClient = null;
_options = new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud };
_clientSecretCredential = new ClientSecretCredential(
this.FindString(config.TenentID)
, this.FindString(config.AppID)
, this.FindString(config.Secret)
, _options);
string[] apiScope = new string[] { this.FindString(config.Scope) };
_token = _clientSecretCredential.GetToken(new Azure.Core.TokenRequestContext(apiScope));
graphServiceClient = new GraphServiceClient(_clientSecretCredential, apiScope);
IUserRequestBuilder _journalMailbox = _graphServiceClient.Users["journal#mycompany.com"];
The code seems correct, but everytime I execute "_journalMailbox.MailFolders.Inbox.Request().CreateAsync", I get the following error:
Code: ErrorInvalidRequest
Message: The OData request is not supported.
ClientRequestId:Some Guid.
From what I could figure out by searching on the internet, it has to do with the method using the wrong method to access the API. I mean like, its using "GET" in stead of "POST" or something like that, but that would mean its a bug in the MS code, and that would an unimaginably big oversight on Microsoft's part, so I can't think its that.
I've tried searching documentation on how to create subfolders, but of the preciously few results I'm getting, almost none has C# code samples, and of those, all are of the previous version of Microsoft Graph.
I'm really stumped here, I'm amazed at how hard it is to find any documentation to do something that is supposed to be simple and straight forward.
Ok, so it turned out that I was blind again. Here is the correct code for what I was trying to do:
private void SetupMailBoxes()
{
SmartLog.EnterMethod("SetupMailBoxes()");
MailFolder inbox = null;
try
{
bool dbErrorFolder = false;
bool exchangeErrorFolder = false;
inbox = _journalMailbox.MailFolders.Inbox.Request().GetAsync().GetAwaiter().GetResult();
if (inbox.ChildFolderCount > 0)
{
inbox.ChildFolders = _journalMailbox.MailFolders.Inbox.ChildFolders.Request().GetAsync().GetAwaiter().GetResult();
}
if (inbox.ChildFolders != null)
{
for (int i = 0; i < inbox.ChildFolders.Count && (!dbErrorFolder || !exchangeErrorFolder); i++)
{
if (inbox.ChildFolders[i].DisplayName.ToLower() == "db-error-items")
{
dbErrorFolder = true;
}
else if (inbox.ChildFolders[i].DisplayName.ToLower() == "exchange-error-items")
{
exchangeErrorFolder = true;
}
}
}
else
{
inbox.ChildFolders = new MailFolderChildFoldersCollectionPage();
}
if (!dbErrorFolder)
{
try
{
var folder = new MailFolder()
{
DisplayName = "DB-Error-Items",
IsHidden = false,
ParentFolderId = inbox.Id
};
folder = _journalMailbox.MailFolders[inbox.Id].ChildFolders.Request().AddAsync(folder).GetAwaiter().GetResult();
inbox.ChildFolders.Add(folder);
}
catch (Exception ex)
{
throw;
}
}
}
catch (Exception exp)
{
SmartLog.LeaveMethod("SetupMailBoxes()");
throw;
}
finally
{
}
SmartLog.LeaveMethod("SetupMailBoxes()");
}
I'm trying to get group list while authenticating user. And still getting 0 results. I unfortunately have no environment for testing, so I cannot debug this code (only via logger). Have no results and no exceptions.
private LdapResponce IsAuthenticated(string ldap, string usr, string pwd, out List<string> groups)
{
List<string> result = new List<string>();
try
{
using (var searcher = new DirectorySearcher(new DirectoryEntry(ldap, usr, pwd)))
{
searcher.Filter = String.Format("(&(objectCategory=group)(member={0}))", usr);
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("cn");
_loggingService.Info(searcher.FindAll().Count.ToString());// here i'm getting 0
foreach (SearchResult entry in searcher.FindAll())
{
try
{
if (entry.Properties.Contains("cn"))
result.Add(entry.Properties["cn"][0].ToString());
}
catch (NoMatchingPrincipalException pex)
{
continue;
}
catch (Exception pex)
{
continue;
}
}
}
groups = result;
}
catch (DirectoryServicesCOMException cex)
{
groups = new List<string>();
if (cex.ErrorCode == -2147023570) return LdapResponce.WrongPassword;
return LdapResponce.Error;
}
catch (Exception ex)
{
groups = new List<string>();
return LdapResponce.Error;
}
return LdapResponce.Passed;
}
Add this to the top of your program
using System.DirectoryServices.AccountManagement;
Use this function and pass the username and the group you are looking to see if they are in. if the group has a group nested it will look in the nested group to see if the user is in that group too.
public static Boolean fctADIsInGroup(string LSUserName, string LSGroupName)
{
Boolean LBReturn = false;
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "Put your domain name here. Right click on My computer and go to properties to see the domain name");
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, LSUserName);
// find the group in question
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, LSGroupName);
if (user != null)
{
// check if user is member of that group
if (user.IsMemberOf(group))
{
LBReturn = true;
}
else
{
var LSAllMembers = group.GetMembers(true);
foreach(var LSName in LSAllMembers)
{
string LSGPUserName = LSName.SamAccountName.ToUpper();
if (LSGPUserName == PSUserName.ToUpper())
{
LBReturn = true;
}
}
}
}
return LBReturn;
}
I'm trying to implement the IsActive method described here Is Account Active, but i'm getting an object reference not set to an instance of the the object.
private bool IsActive(DirectoryEntry de)
{
DirectoryEntry myEntry = GetDirectoryEntry();
if (myEntry.NativeGuid == null) return false;
int flags = (int)myEntry.Properties["userAccountControl"].Value;
if (!Convert.ToBoolean(flags & 0x0002)) return true; else return false;
return false;
}
private void SubmitData()
{
System.Guid guid = Guid.NewGuid();
logInfo.IPaddress = IPAddress;
if (!String.IsNullOrEmpty(txtUser.Text))
{
string username = txtUser.Text.ToString();
if (IsActive(de) != false)
{
if (DateTime.Now.Subtract(passwordLastSet).TotalHours > 1)
{
lblPasswordLastSet.Text = passwordLastSet.ToString();
lblStatus.Text = "all is good";
}
else
{
lblStatus.Text = "oops, you reset your password less than 24 hours ago!";
lblPasswordLastSet.Text = passwordLastSet.ToString();
}
}
else
{
lblStatus.Text = "your account is not active";
}
}
}
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find user by name
UserPrincipal user = UserPrincipal.FindByIdentity("John Doe");
if(user != null)
{
// check if account is locked out
if(user.IsAccountLockedOut)
{
// do something if locked out....
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
I know that this type of question has been asked before, but other methods are failing me right now.
As it stands our windows service polls AD, given an LDAP (i.e. LDAP://10.32.16.80) and a list of usergroups within that AD server to search for.
It retrieves all users within those given groups, recursively searching those groups for more groups as well.
Each user is then added to another applications authenticated users list.
This part of the application is running successfully. However, we're in need of each user's friendly domain name (i.e. the part of their login DOMAIN/username)
So if there is a user that is part of TEST domain, named Steve: TEST/steve is his login.
I'm able to find steve in the AD, however I also need "TEST" to be stored along with his AD information.
Again, I can find 'steve' fine by using a directory searcher and the LDAP IP I'm given, but given the LDAP IP, how can I find the friendly domain name?
When I try the following code I'm given an error when attempting to access the 'defaultNamingContext':
System.Runtime.InteropServices.COMException (0x8007202A): The authentication mechanism is unknown.
Here is the code:
private string SetCurrentDomain(string server)
{
string result = string.Empty;
try
{
logger.Debug("'SetCurrentDomain'; Instantiating rootDSE LDAP");
DirectoryEntry ldapRoot = new DirectoryEntry(server + "/rootDSE", username, password);
logger.Debug("'SetCurrentDomain'; Successfully instantiated rootDSE LDAP");
logger.Debug("Attempting to retrieve 'defaultNamingContext'...");
string domain = (string)ldapRoot.Properties["defaultNamingContext"][0]; //THIS IS WHERE I HIT THE COMEXCEPTION
logger.Debug("Retrieved 'defaultNamingContext': " + domain);
if (!domain.IsEmpty())
{
logger.Debug("'SetCurrentDomain'; Instantiating partitions/configuration LDAP entry");
DirectoryEntry parts = new DirectoryEntry(server + "/CN=Partitions,CN=Configuration," + domain, username, password);
logger.Debug("'SetCurrentDomain'; Successfully instantiated partitions/configuration LDAP entry");
foreach (DirectoryEntry part in parts.Children)
{
if (part.Properties["nCName"] != null && (string)part.Properties["nCName"][0] != null)
{
logger.Debug("'SetCurrentDomain'; Found property nCName");
if ((string)part.Properties["nCName"][0] == domain)
{
logger.Debug("'SetCurrentDomain'; nCName matched defaultnamingcontext");
result = (string)part.Properties["NetBIOSName"][0];
logger.Debug("'SetCurrentDomain'; Found NetBIOSName (friendly domain name): " + result);
break;
}
}
}
}
logger.Debug("finished setting current domain...");
}
catch (Exception ex)
{
logger.Error("error attempting to set domain:" + ex.ToString());
}
return result;
}
edit
I added this sample method in order to attempt a suggestion but am getting an exception: "Unspecified error" when I hit the "FindAll()" call on the searcher.
The string being passed in is: "CN=TEST USER,CN=Users,DC=tempe,DC=ktregression,DC=com"
private string GetUserDomain(string dn)
{
string domain = string.Empty;
string firstPart = dn.Substring(dn.IndexOf("DC="));
string secondPart = "CN=Partitions,CN=Configuration," + firstPart;
DirectoryEntry root = new DirectoryEntry(secondPart, textBox2.Text, textBox3.Text);
DirectorySearcher searcher = new DirectorySearcher(root);
searcher.SearchScope = SearchScope.Subtree;
searcher.ReferralChasing = ReferralChasingOption.All;
searcher.Filter = "(&(nCName=" + firstPart + ")(nETBIOSName=*))";
try
{
SearchResultCollection rs = searcher.FindAll();
if (rs != null)
{
domain = GetProperty(rs[0], "nETBIOSName");
}
}
catch (Exception ex)
{
}
return domain;
This article helped me much to understand how to work with the Active Directory.
Howto: (Almost) Everything In Active Directory via C#
From this point forward, if you require further assitance, please let me know with proper questions in comment, and I shall answer them for you to the best of my knowledge.
EDIT #1
You had better go with this example's filter instead. I have written some sample code to briefly show how to work with the System.DirectoryServices and System.DirectoryServices.ActiveDirectory namespaces. The System.DirectoryServices.ActiveDirectory namespace is used to retrieve information about the domains within your Forest.
private IEnumerable<DirectoryEntry> GetDomains() {
ICollection<string> domains = new List<string>();
// Querying the current Forest for the domains within.
foreach(Domain d in Forest.GetCurrentForest().Domains)
domains.Add(d.Name);
return domains;
}
private string GetDomainFullName(string friendlyName) {
DirectoryContext context = new DirectoryContext(DirectoryContextType.Domain, friendlyName);
Domain domain = Domain.GetDomain(context);
return domain.Name;
}
private IEnumerable<string> GetUserDomain(string userName) {
foreach(string d in GetDomains())
// From the domains obtained from the Forest, we search the domain subtree for the given userName.
using (DirectoryEntry domain = new DirectoryEntry(GetDomainFullName(d))) {
using (DirectorySearcher searcher = new DirectorySearcher()){
searcher.SearchRoot = domain;
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("sAMAccountName");
// The Filter is very important, so is its query string. The 'objectClass' parameter is mandatory.
// Once we specified the 'objectClass', we want to look for the user whose login
// login is userName.
searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", userName);
try {
SearchResultCollection results = searcher.FindAll();
// If the user cannot be found, then let's check next domain.
if (results == null || results.Count = 0)
continue;
// Here, we yield return for we want all of the domain which this userName is authenticated.
yield return domain.Path;
} finally {
searcher.Dispose();
domain.Dispose();
}
}
}
Here, I didn't test this code and might have some minor issue to fix. This sample is provided as-is for the sake of helping you. I hope this will help.
EDIT #2
I found out another way out:
You have first to look whether you can find the user account within your domain;
If found, then get the domain NetBIOS Name; and
concatenate it to a backslash (****) and the found login.
The example below uses a NUnit TestCase which you can test for yourself and see if it does what you are required to.
[TestCase("LDAP://fully.qualified.domain.name", "TestUser1")]
public void GetNetBiosName(string ldapUrl, string login)
string netBiosName = null;
string foundLogin = null;
using (DirectoryEntry root = new DirectoryEntry(ldapUrl))
Using (DirectorySearcher searcher = new DirectorySearcher(root) {
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("sAMAccountName");
searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", login);
SearchResult result = null;
try {
result = searcher.FindOne();
if (result == null)
if (string.Equals(login, result.GetDirectoryEntry().Properties("sAMAccountName").Value))
foundLogin = result.GetDirectoryEntry().Properties("sAMAccountName").Value
} finally {
searcher.Dispose();
root.Dispose();
if (result != null) result = null;
}
}
if (!string.IsNullOrEmpty(foundLogin))
using (DirectoryEntry root = new DirectoryEntry(ldapUrl.Insert(7, "CN=Partitions,CN=Configuration,DC=").Replace(".", ",DC="))
Using DirectorySearcher searcher = new DirectorySearcher(root)
searcher.Filter = "nETBIOSName=*";
searcher.PropertiesToLoad.Add("cn");
SearchResultCollection results = null;
try {
results = searcher.FindAll();
if (results != null && results.Count > 0 && results[0] != null) {
ResultPropertyValueCollection values = results[0].Properties("cn");
netBiosName = rpvc[0].ToString();
} finally {
searcher.Dispose();
root.Dispose();
if (results != null) {
results.Dispose();
results = null;
}
}
}
Assert.AreEqual("FULLY\TESTUSER1", string.Concat(netBiosName, "\", foundLogin).ToUpperInvariant())
}
The source from which I inspired myself is:
Find the NetBios Name of a domain in AD
Since I could not find any example code I would like to share my own solution. This will search the parents of the DirectoryEntry object until it hits the domainDNS class.
using System.DirectoryServices;
public static class Methods
{
public static T ldap_get_value<T>(PropertyValueCollection property)
{
object value = null;
foreach (object tmpValue in property) value = tmpValue;
return (T)value;
}
public static string ldap_get_domainname(DirectoryEntry entry)
{
if (entry == null || entry.Parent == null) return null;
using (DirectoryEntry parent = entry.Parent)
{
if (ldap_get_value<string>(parent.Properties["objectClass"]) == "domainDNS")
return ldap_get_value<string>(parent.Properties["dc"]);
else
return ldap_get_domainname(parent);
}
}
}
Use it like this:
string[] _properties = new string[] { "objectClass", "distinguishedName", "samAccountName", "userPrincipalName", "displayName", "mail", "title", "company", "thumbnailPhoto", "useraccountcontrol" };
string account = "my-user-name";
// OR even better:
// string account = "my-user-name#DOMAIN.local";
using (DirectoryEntry ldap = new DirectoryEntry())
{
using (DirectorySearcher searcher = new DirectorySearcher(ldap))
{
searcher.PropertiesToLoad.AddRange(_properties);
if (account.Contains('#')) searcher.Filter = "(userPrincipalName=" + account + ")";
else searcher.Filter = "(samAccountName=" + account + ")";
var user = searcher.FindOne().GetDirectoryEntry();
Console.WriteLine("Name: " + Methods.ldap_get_value<string>(user.Properties["displayName"]));
Console.WriteLine("Domain: " + Methods.ldap_get_domainname(user));
Console.WriteLine("Login: " + Methods.ldap_get_domainname(user) + "\\" + Methods.ldap_get_value<string>(user.Properties["samAccountName"]));
}
}
I haven't got a forest to test it on but in theory this should cut it.
You can retrieve the name of the domain that the current user is on using the Environment.UserDomainName Property.
string domainName;
domainName = System.Environment.UserDomainName;
Maybe not entirely correct but...
DirectoryEntry dirEntry = new DirectoryEntry();
DirectorySearcher dirSearcher = new DirectorySearcher(dirEntry);
dirSearcher.SearchScope = SearchScope.Subtree;
dirSearcher.Filter = string.Format("(&(objectClass=user)(|(cn={0})(sn={0}*)(givenName={0})(sAMAccountName={0}*)))", userName);
var searchResults = dirSearcher.FindAll();
foreach (SearchResult sr in searchResults)
{
var de = sr.GetDirectoryEntry();
string user = de.Properties["SAMAccountName"][0].ToString();
string domain = de.Path.ToString().Split(new [] { ",DC=" },StringSplitOptions.None)[1];
MessageBox.Show(domain + "/" + user);
}
Because the value of de.Path is
LDAP://CN=FullName,DC=domain,DC=local
I'm using the System.DirectoryServices.AccountManagement namespace classes to manage the membership of several groups. These groups control the population of our print accounting system and some of them are very large. I'm running into a problem removing any user from one of these large groups. I have a test program that illustrates the problem. Note that the group I'm testing is not nested, but user.IsMemberOf() also seems to have the same problem, whereas GetAuthorizationGroups() correctly shows the groups a user is a member of. The group in question has about 81K members, which is more than it should have since Remove() isn't working, and will normally be about 65K or so.
I'd be interested to hear from other people who have had this problem and have resolved it. I've got an open case with Microsoft, but the turn around on the call is slow since the call center is about 17 hours time difference so they don't arrive for work until about an hour before I usually leave for home.
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var group = GroupPrincipal.FindByIdentity( context, groupName ))
{
using (var user = UserPrincipal.FindByIdentity( context, userName ))
{
if (user != null)
{
var isMember = user.GetAuthorizationGroups()
.Any( g => g.DistinguishedName == group.DistinguishedName );
Console.WriteLine( "1: check for membership returns: {0}", isMember );
if (group.Members.Remove( user ))
{
Console.WriteLine( "user removed successfully" );
group.Save();
}
else
{
// do save in case Remove() is lying to me
group.Save();
Console.WriteLine( "user remove failed" );
var isStillMember = user.GetAuthorizationGroups()
.Any( g => g.DistinguishedName == group.DistinguishedName );
Console.WriteLine( "2: check for membership returns: {0}", isStillMember );
}
}
}
}
}
Turns out this is a bug in the GroupPrincipal.Members.Remove() code in which remove fails for a group with more than 1500 members. This has been fixed in .NET 4.0 Beta 2. I don't know if they have plans to back port the fix into 2.0/3.x.
The work around is to get the underlying DirectoryEntry, then use Invoke to execute the Remove command on the IADsGroup object.
var entry = group.GetUnderlyingObject() as DirectoryEntry;
var userEntry = user.GetUnderlyingObject() as DirectoryEntry;
entry.Invoke( "Remove", new object[] { userEntry.Path } );
This post helped point me in the right direction, just wanted to add some addition info.
It also works binding directly to the group, and you can use it for adding group members.
using (var groupEntry = new DirectoryEntry(groupLdapPath))
{
groupEntry.Invoke("remove", new object[] { memberLdapPath });
groupEntry.Invoke("add", new object[] { memberLdapPath });
}
Also be aware, with the standard 'member' attribute, you use the user or group distinguishedName, but invoke requires the path with LDAP:// prefix, otherwise it throws a vague InnerException:
Exception from HRESULT: 0x80005000
public bool RemoveUserFromGroup(string UserName, string GroupName)
{
bool lResult = false;
if (String.IsNullOrEmpty(UserName) || String.IsNullOrEmpty(GroupName)) return lResult;
try
{
using (DirectoryEntry dirEntry = GetDirectoryEntry())
{
using (DirectoryEntry dirUser = GetUser(UserName))
{
if (dirEntry == null || dirUser == null)
{
return lResult;
}
using (DirectorySearcher deSearch = new DirectorySearcher())
{
deSearch.SearchRoot = dirEntry;
deSearch.Filter = String.Format("(&(objectClass=group) (cn={0}))", GroupName);
deSearch.PageSize = 1000;
SearchResultCollection result = deSearch.FindAll();
bool isAlreadyRemoved = false;
String sDN = dirUser.Path.Replace("LDAP://", String.Empty);
if (result != null && result.Count > 0)
{
for (int i = 0; i < result.Count; i++)
{
using (DirectoryEntry dirGroup = result[i].GetDirectoryEntry())
{
String sGrDN = dirGroup.Path.Replace("LDAP://", String.Empty);
if (dirUser.Properties[Constants.Properties.PROP_MEMBER_OF].Contains(sGrDN))
{
dirGroup.Properties[Constants.Properties.PROP_MEMBER].Remove(sDN);
dirGroup.CommitChanges();
dirGroup.Close();
lResult = true;
isAlreadyRemoved = true;
break;
}
}
if (isAlreadyRemoved)
break;
}
}
}
}
}
}
catch
{
lResult= false;
}
return lResult;
}