DirectorySearcher.ClientTimeout - Search timed out or really 0 results? - c#

I maintain a C# program which needs to check whether thousands of Active Directory accounts are still in existence & if they are enabled or not. Recently, I've found that my program was hanging while querying the directory for an account. Fortunately, I've just discovered the DirectorySearcher.ClientTimeout property (which I hadn't been setting, meaning that the search goes on indefinitely).
The one problem that I see with using this property is that, if the search hangs while looking up an account that happens to actually exist, the DirectorySearcher.FindOne() method will return 0 results. As you can imagine, that's a problem since at runtime, I don't know whether the search failed or if the account really wasn't found.
Does anyone know if there's another property that gets set in the object that I can use to see if the search aborted? Is there any difference between a result set from an aborted search versus one that really contains zero results?
Here's my method:
public static string UserExists(string username, Log log)
{
string accountStatus;
if (username.Split('\\').Length != 2)
return "Invalid ID,Invalid ID";
else
{
try
{
string[] parts = username.Split('\\');
domain = parts[0];
ScopeDN = "DC=" + domain + ",DC=contoso,DC=com";
DirectoryEntry de = GetDirectoryEntry("LDAP://" + ScopeDN);
DirectorySearcher ds = new DirectorySearcher();
ds.SearchRoot = de;
ds.ClientTimeout = TimeSpan.FromSeconds(30);
ds.Filter = "(&(objectClass=user) (sAMAccountName=" + username + "))";
SearchResult result = ds.FindOne();
if (result == null)
accountStatus = "Does Not Exist,Account Does Not Exist";
else
{
int UAC = (int)result.Properties["userAccountControl"][0];
bool enabled = !Convert.ToBoolean(UAC & 0x00002);
if (enabled)
accountStatus = "Exists,Account is Enabled";
else
accountStatus = "Exists,Account is Disabled";
}
return accountStatus;
}
catch (Exception e)
{
log.exception(LogLevel._ERROR, e, false);
return "Exception,Exception";
}
}
}

Try doing the following optimizations:
Dispose DirectoryEntry
Dispose DirectorySearcher
Create and cache a connection to RootDse object before querying users. In this case all AD queries will use one single cached connection to AD, which significantly increases performance.
For example
var entry = new DirectoryEntry("LDAP://contoso.com/RootDSE");
entry.RefreshCache();
// making user search and other AD work with domain contoso.com here
entry.Dispose();

AFAIK, there is no difference. One thing you could do is retry a couple times before assuming it is missing. Its a matter of confidence. One failure may not give high enough confidence. But 2 or 3 might. Does that help you?

Related

to retrieve windows login-password via asp.net

public Object IsAuthenticated()
{
String domainAndUsername = strDomain + "\\" + strUser;
***DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, strPass);***
SearchResult result;
try
{
//Bind to the native AdsObject to force authentication.
DirectorySearcher search = new DirectorySearcher(entry) { Filter = ("(SAMAccountName=" + strUser + ")") };
search.PropertiesToLoad.Add("givenName"); // First Name
search.PropertiesToLoad.Add("sn"); // Last Name
search.PropertiesToLoad.Add("cn"); // Last Name
result = search.FindOne();
if (null == result)
{
return null;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
return new Exception("Error authenticating user. " + ex.Message);
}
return user;
}
In the above code segment, is there a way to retrieve the user's Windows login password so that the LDAP authentication works without asking the user his password another time?
Can the value for "strPass",that is being passed when DirectoryEntry object is being created, be retrieved by any way?
The password does not exist anywhere. It would be a big security hole if it did.
Also, BTW, get rid of the try/catch block. It's doing nothing but hiding the reason for the exception.
Use the ActiveDirectoryMemebershipProvider - You can authenticate without writing code, thus eliminating the login scenario you currently have.
You could set up Windows Authentication in your ASP.NET app.
http://msdn.microsoft.com/en-us/library/ff647405.aspx
Once you set this up, only authenticated users have access to the protected parts of your site.
This gives you access some key bits of information.
For example:
System.Web.HttpContext.Current.User.Identity.Name - gives the name (domain\username) of an authenticated user.
System.Web.HttpContext.Current.User.IsInRole("role_name_here") - will tell you if the authenticated user is in a given role.
Authentication can be tricky to do for the first time - please do not ask a user for their windows password - this is a security risk - allow IIS and the .NET framework take care of this for you. The article above may be a bit long and seem a bit complicated but it has a lot of good information in it.

System.DirectoryServices.DirectoryServicesCOMException (0x8007203B): A local error has occurred

When we try to search for a user in ActiveDirectory, we get that exception - 0x8007203B.
Basically we deployed a web service, which uses DirectoryEntry & DirectorySearcher class to find a user in AD, and sometimes this exception happens. But when we do IISReset, it again works fine.
Code is very simple like this:
DirectoryEntry domainUser = new DirectoryEntry("LDAP://xxx.yyy/dc=xxx,dc=yyy", "domain\user", "pwd", AuthenticationTypes.Secure);
DirectoryEntry result = new DirectorySearcher(domainUser, filter);
Only some times this happens. I don't have much information to provide, any guess much appreciated
This is how my filter looks like
public static string BuildFilter(DirectoryEntry dirEntry, string userName, string userMail)
{
try
{
string filter = string.Empty;
if (!string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(userMail))
filter = string.Format(#"(&(objectClass=user)(samaccounttype=805306368)(|(CN={0})(samaccountname={0})))", userName);
else if (string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(userMail))
filter = string.Format(#"(&(objectClass=user)(samaccounttype=805306368)(mail={0}))", userMail);
else
filter = string.Format(#"(&(objectClass=user)(samaccounttype=805306368)(|(CN={0})(samaccountname={0})(mail={1})))", userName, userMail);
return filter;
}
catch (Exception ex)
{
_logger.Error("BuildUserSearch - Failed to build LDAP search", ex);
}
return null;
}
You say that this it's just append after some time. As DirectoryEntry and DirectorySearcher are built on COM object into disposable class I would first just add some using sections to be sure that underlying objects are corectly freed.
using(DirectoryEntry root = new DirectoryEntry(ldapPath))
{
using(DirectorySearcher searcher=new DirectorySearcher(root))
{
...
}
...
}
Any guess are appreciated?
Then here's mine:
ASP.NET: DirectoryServicesCOMException [...];
Windows Error Codes: Repair 0x8007203B. How To Repair 0x8007203B.
What makes me confuse is that you say it works most of the time...
Did this help?
P.S. I'll update if I think of anything else.

Adding User to AD Security Group fails after user creation

I am using a WCF service to expose certain Active Directory management functions to our help desk staff without giving them the group membership required to manipulate AD directly. Adding users to and removing users from groups is working like a champ with existing users, but every time I create a new user it throws back this fun code:
The server is unwilling to process the request. (Exception from HRESULT: 0x80072035)
The code I use to add the user to the group is
public bool AddGroupToUser(string userDn, string groupDn)
{
try
{
DirectoryEntry groupEntry = LdapTools.GetDirectoryEntry(groupDn);
groupEntry.Properties["member"].Add(userDn);
groupEntry.CommitChanges();
groupEntry.Close();
return true;
}
catch (DirectoryServicesCOMException)
{
return false;
}
}
Everything I've read on the subject is rather vague and I can't seem to find out WHY the exception is being triggered. Any ideas?
UPDATE
This is the code I use to create the user in AD:
try
{
DirectoryEntry container = GetDirectoryEntry(storageOu);
DirectoryEntry newUser = container.Children.Add("CN=" + employee.FullName, "user");
newUser.Properties["sAMAccountName"].Value = employee.Username;
newUser.Properties["displayName"].Value = employee.FullName;
newUser.Properties["givenName"].Value = employee.FirstName;
newUser.Properties["sn"].Value = employee.LastName;
newUser.Properties["department"].Value = departmentName;
newUser.Properties["userPrincipalName"].Value = employee.Username + "#APEX.Local";
newUser.CommitChanges();
newUser.Invoke("SetPassword", new object[] { employee.Password });
newUser.CommitChanges();
AdsUserFlags userSettings = AdsUserFlags.NormalAccount;
newUser.Properties["userAccountControl"].Value = userSettings;
newUser.CommitChanges();
ldapPath = newUser.Path;
newUser.Close();
container.Close();
}
catch (DirectoryServicesCOMException e)
{
// Something went wrong... what???
}
catch (Exception e)
{
// Something else went wrong
}
The new user can login, and can be manipulated using the standard MS tools.
Apparently, unless I'm missing an important step here, the issue is time. When forcing the system to sleep for 8 seconds before attempting to add groups to the new user the process works. If I do it any sooner than the 8 second mark it fails.
I'm marking this answer as correct unless anybody can provide me a better solution.
Try:
public bool AddUserToGroup(string userName, string groupName)
{
bool done = false;
GroupPrincipal group = GroupPrincipal.FindByIdentity(context, groupName);
if (group == null)
{
group = new GroupPrincipal(context, groupName);
}
UserPrincipal user = UserPrincipal.FindByIdentity(context, userName);
if (user != null & group != null)
{
group.Members.Add(user);
group.Save();
done = (user.IsMemberOf(group));
}
return done;
}
Reference:
http://www.c-sharpcorner.com/UploadFile/dhananjaycoder/activedirectoryoperations11132009113015AM/activedirectoryoperations.aspx
As it mentioned in here, can you tell us that you set the password of the newly created users? In the reference, it says that you should SetPassword of the user before doing anything with it.
Time issue could occur from a Active Directory replication issue.
Once, I gave a product to my customer which creates user over Active Directory, which has over 10.000 records in it, with the given informations on a SharePoint form and then program adds the user to a SharePoint group. The problem was, SharePoint throws an error about the newly created user, it says user does not exist in AD.
So, one of out system engineer told us about the replication opearation on the Active Directory that it could be the source of the problem and it was true.
For the solution, program tries to do the job for 10 times with 1 second sleeps. No problem occurred so far. So as a solution, I would suggest you to check out the AD for the existence of replication method.
P.S: When I asked questions to my customers system engineers about the replication operation, they all rejected the existence of AD replication operation and told me that the program has the problem. They believed me when we created a new user in AD from a computer, we couldn't see the user for 5 seconds on an another computer.

ActiveDirectory Local Machine Account management - C#

I posted a question re LDAP account management, but after exploring this, it's not what i'm after. I've managed to find two ways of creating users on a machine, and i find one is much neater than the other, however, i am uncertain how to convert the first option over to the second option entirely.
This was my first solution:
Process MyProc = new Process();
MyProc.StartInfo.WorkingDirectory = System.Environment.SystemDirectory;
MyProc.StartInfo.FileName = "net.exe";
MyProc.StartInfo.UseShellExecute = false;
MyProc.StartInfo.RedirectStandardError = true;
MyProc.StartInfo.RedirectStandardInput = true;
MyProc.StartInfo.RedirectStandardOutput = true;
MyProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
MyProc.StartInfo.Arguments = string.Format(#" user {0} {1} /ADD /ACTIVE:YES /EXPIRES:NEVER /FULLNAME:{0}"" /PASSWORDCHG:NO /PASSWORDREQ:YES", username, password);
MyProc.Start();
MyProc.WaitForExit();
int exit = MyProc.ExitCode;
MyProc.Close();
return exit == 0;
And this was my second (preffered) solution:
DirectoryEntry root = GetDELocalRoot();
DirectoryEntry user = root.Children.Add(username, "user");
//TODO: Always Active
//TODO: Never Expires
//TODO: No Password Change
//TODO: Password Required
user.Properties["description"].Value = "Account for running the MicaService and handling updates.";
user.Invoke("SetPassword", new object[] { password });
user.CommitChanges();
user.Close();
I would like to map all the settings in my TODO: from the first solution into my second neater solution.
I have tried the following line as well:
user.Properties["userAccountControl"].Value = ADS_USER_FLAG.ADS_UF_NORMAL_ACCOUNT | ADS_USER_FLAG.ADS_UF_PASSWD_CANT_CHANGE | ADS_USER_FLAG.ADS_UF_DONT_EXPIRE_PASSWD;
But this does not work as the property does not exist in cache.
NOTE: the GetDELocalRoot() = return new DirectoryEntry("WinNT://" + Environment.MachineName);
Thanks for any input!
Regards
Tris
Check out my friend Richard Mueller's web site which has lots of useful information and reference material on what those two providers - WinNT for local machine accounts vs. LDAP for network accounts - have to offer.
There's also a Excel sheeet with all attributes that the WinNT provider exposes - it's a lot less than what the LDAP provider has, so I'm not sure if you'll be able to set all the properties you're looking for.
Marc

ADAM authentication - howto?

I'm attempting to authenticate a user against ADAM using a user I created in ADAM. However, regardless of the password used (correct, or incorrect), my search comes back with a valid DirectoryEntry object. I would assume that if the password is invalid, then the search would come back with a null object. Are my assumptions wrong or is there a flaw in the code below?
DirectoryEntry de = new DirectoryEntry("LDAP://localhost:389/cn=Groups,cn=XXX,cn=YYY,dc=ZZZ");
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(&(objectClass=user) (cn=" + userId + "))";
SearchResultCollection results = deSearch.FindAll();
if (results.Count > 0)
{
DirectoryEntry d = new DirectoryEntry(results[0].Path, userId, password);
if (d != null)
DoSomething();
}
You need to access a property of the DirectoryEntry to determine if it's valid. I usually check if the Guid is null or not.
bool valid = false;
using (DirectoryEntry entry = new DirectoryEntry( results[0].Path, userId, password ))
{
try
{
if (entry.Guid != null)
{
valid = true;
}
}
catch (NullReferenceException) {}
}
Note: you'll also want to wrap your search root directory entry and searcher in using statements, or explicitly dispose of them when you are done so that you don't leave the resources in use.
P.S. I'm not sure exactly which exception gets thrown when you attempt to access an invalid directory entries properties. A bit of experimentation is probably in order to figure out which exception to catch. You won't want to catch all exceptions as there are other problems (directory server not available, for instance) that you may want to handle differently than a failed authentication.

Categories