Fetching user details from AD - c#

In my module I have a TextBox, Button and ListBox. Whenever button is pressed, I have to fetch the details of the user (fetched from the text box) and I need to add in the list box. This working now.
My problem is that if I press the button, the first time nothing happens - but if the same button is pressed a second time, I get the value of the user name in the list box.
Please help me identify the actual cause of why its not working on the first click. As of now I am clicking two times for every record.
protected void Add_Click(object sender, EventArgs e)
{
try
{
DirectoryEntry entry1 = new DirectoryEntry("LDAP://corp.com", "123456", "password");
DirectorySearcher dSearch1 = new DirectorySearcher(entry1);
dSearch1.Filter = "(&(objectClass=user)(samAccountName=" + Additional_ID.Text + "))";
dSearch1.PropertiesToLoad.Add("cn");
dSearch1.PropertiesToLoad.Add("mail");
SearchResult result = dSearch1.FindOne();
if (result.Properties["mail"] != null)
{
ListItem L = new ListItem();
L.Text = "" + result.Properties["cn"][0].ToString();
L.Value = "" + result.Properties["mail"][0].ToString();
New_Users_list.Items.Add(L);
Additional_ID.Text = "";
}
entry1.Close();
dSearch1.Dispose();
}
catch
{
Additional_ID.Text = "Enter Valid ID";
}
}

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
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

Related

Get the last login date for Active Directory from a list of organizational unit users in c#

I have my code done that reads all users from a organizational unit, now I need to be able to read the list all those users and display the last login of each user.
Here is the code that reads correctly all the users I need
private void btn_LastLoginUser_Click(object sender, EventArgs e)
{
GroupUsers();
}
public void GroupUsers()
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "mydomain", "OU=myEmployees,DC=myCompany,DC=com");
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.Enabled = true;
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach (var found in srch.FindAll())
{
lst_Users.Items.Add(found);
}
}
I need help reading that data and now display the last login date for each user and populate it to my listbox.
FunCoder,
This should allow you to get the Last Login Date and Time from the DC. It will convert the user found to an authenticated principle which contains a number of different properties that might help with other information you might be looking for in the future.
foreach(var found in srch.FindAll())
{
var auth = found as AuthenticablePrincipal;
if(auth != null)
{
var Name = auth.Name;
var LastLogin = auth.LastLogon;
}
}

C# Can not add user to Active Directory. It show error There is no such object on the server

I want to use C# add user to Active Directory from my computer.
AD install in server 10.1.1.5
Username and password for login to AD is "Administrator", "Pass12345678".
I create OU name Guest on AD.
This is my code to add user to AD.
private void Btn_ok_Click(object sender, EventArgs e)
{
UserInfo newUserInfo;
newUserInfo.firstName = "TestName" ;
newUserInfo.lastName = "TestLastName";
newUserInfo.displayName = "TestName Lastname";
newUserInfo.username = "TestName.t";
newUserInfo.sAMAccountName = "TestName.t";
DirectoryEntry adUserFolder = new DirectoryEntry("LDAP://10.1.1.5/CN=Users,OU=Guest,DC=myhome,DC=com", "Administrator", "Pass12345678");
/******** It show error this line **********/
if(adUserFolder.SchemaEntry.Name == "container")
{
DirectoryEntry newUser = adUserFolder.Children.Add("CN=" + newUserInfo.username, "User");
if (DirectoryEntry.Exists(newUser.Path))
{
//Remove exist user
adUserFolder.Children.Remove(new DirectoryEntry(newUser.Path));
}
newUser.Properties["sAMAccountName"].Value = newUserInfo.sAMAccountName;
newUser.Properties["givenName"].Value = newUserInfo.firstName;
newUser.Properties["sn"].Value = newUserInfo.lastName;
newUser.Properties["displayName"].Value = newUserInfo.displayName;
newUser.CommitChanges();
newUser.Invoke("setpassword", "Test123456");
newUser.CommitChanges();
}
}
when I click OK button it show error.
System.DirectoryServices.DirectoryServicesCOMException: 'There is no
such object on the server. '
at line
if(adUserFolder.SchemaEntry.Name == "container")
How to fix it ?
The DirectoryEntry constructor will never throw an exception. It doesn't actually connect to AD until you start using it. Since that line is the first time you are using adUserFolder, that's when it first connects to AD.
The error means what it says: there is no object with a distinguishedName of CN=Users,OU=Guest,DC=myhome,DC=com.
Are you sure you have that right? It seems like you are looking for a container called Users inside of an OU called Guest. Is that what you are trying to do?
Is Users a container or an OU?
You can confirm the distinguishedName by using AD Users and Computers: navigate to the object you want -> right-click -> 'Properties' -> Attribute Editor tab and look at the distinguishedName attribute.
For creating user objects in active directory I've always used UserPrincipals as opposed to DirectoryEntries:
public void create(string lanid, string new_password, string container)
{
using (UserPrincipal new_user = new UserPrincipal(new PrincipalContext(ContextType.Domain, this.domain_string, container)))
{
new_user.SamAccountName = lanid;
new_user.SetPassword(new_password);
new_user.Enabled = true;
new_user.Save();
}
}
In that example "container" would be something like:
"OU=container,OU=container,OU=container,OU=container,DC=domain,DC=domain,DC=domain";
As for your original error with the DirectoryEntries I'd step through it and see if the object is actually set to anything:
The string:
"LDAP://10.1.1.5/CN=Users,OU=Guest,DC=myhome,DC=com"
Doesn't look right to me;
"LDAP://CN=10.1.1.5,CN=Users,OU=Guest,DC=myhome,DC=com"
May work...

Unlocking user Account

I am trying to set properties to unlock User accounts in AD and I am using the following code; the problem is that de does not contain userAccountControl and the code fails.
I can get the value of userAccountControl by using DirectorySearcher but that does not help me in setting the property using de. Could anyone please help me? Thanks in advance
String m_Path = "LDAP://" + distinguishedName;
using (DirectoryEntry de = new DirectoryEntry(m_Path))
{
if (de.Contains("userAccountControl")
{
int m_Val = (int)de.Properties["userAccountControl"][0].Value;
de.Properties["userAccountControl"].Value = m_Val | 0x0001
de.CommitChanges;
}
}
I would think you need to check whether de.Properties contains a value of userAccountControl!
string ldapPath = "LDAP://" + distinguishedName;
using(DirectoryEntry de = new DirectoryEntry(ldapPath))
{
// check to see if we have "userAccountControl" in the **properties** of de
if(de.Properties.Contains("userAccountControl")
{
int m_Val = (int)de.Properties["userAccountControl"][0].Value ;
de.Properties["userAccountControl"].Value = m_Val | 0x0001;
de.CommitChanges();
}
}
Also, 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
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find and manipulate users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// unlock user
user.UnlockAccount();
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

How to get the groups of a user in Active Directory? (c#, asp.net)

I use this code to get the groups of the current user. But I want to manually give the user and then get his groups. How can I do this?
using System.Security.Principal;
public ArrayList Groups()
{
ArrayList groups = new ArrayList();
foreach (IdentityReference group in System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
{
groups.Add(group.Translate(typeof(NTAccount)).ToString());
}
return groups;
}
If you're on .NET 3.5 or up, you can use the new System.DirectoryServices.AccountManagement (S.DS.AM) namespace which makes this a lot easier than it used to be.
Read all about it here: Managing Directory Security Principals in the .NET Framework 3.5
Update: older MSDN magazine articles aren't online anymore, unfortunately - you'll need to download the CHM for the January 2008 MSDN magazine from Microsoft and read the article in there.
Basically, you need to have a "principal context" (typically your domain), a user principal, and then you get its groups very easily:
public List<GroupPrincipal> GetGroups(string userName)
{
List<GroupPrincipal> result = new List<GroupPrincipal>();
// establish domain context
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
// find your user
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, userName);
// if found - grab its groups
if(user != null)
{
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
// iterate over all groups
foreach(Principal p in groups)
{
// make sure to add only group principals
if(p is GroupPrincipal)
{
result.Add((GroupPrincipal)p);
}
}
}
return result;
}
and that's all there is! You now have a result (a list) of authorization groups that user belongs to - iterate over them, print out their names or whatever you need to do.
Update: In order to access certain properties, which are not surfaced on the UserPrincipal object, you need to dig into the underlying DirectoryEntry:
public string GetDepartment(Principal principal)
{
string result = string.Empty;
DirectoryEntry de = (principal.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
if (de.Properties.Contains("department"))
{
result = de.Properties["department"][0].ToString();
}
}
return result;
}
Update #2: seems shouldn't be too hard to put these two snippets of code together.... but ok - here it goes:
public string GetDepartment(string username)
{
string result = string.Empty;
// if you do repeated domain access, you might want to do this *once* outside this method,
// and pass it in as a second parameter!
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username);
// if user is found
if(user != null)
{
// get DirectoryEntry underlying it
DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
if (de.Properties.Contains("department"))
{
result = de.Properties["department"][0].ToString();
}
}
}
return result;
}
GetAuthorizationGroups() does not find nested groups. To really get all groups a given user is a member of (including nested groups), try this:
using System.Security.Principal
private List<string> GetGroups(string userName)
{
List<string> result = new List<string>();
WindowsIdentity wi = new WindowsIdentity(userName);
foreach (IdentityReference group in wi.Groups)
{
try
{
result.Add(group.Translate(typeof(NTAccount)).ToString());
}
catch (Exception ex) { }
}
result.Sort();
return result;
}
I use try/catch because I had some exceptions with 2 out of 200 groups in a very large AD because some SIDs were no longer available. (The Translate() call does a SID -> Name conversion.)
First of all, GetAuthorizationGroups() is a great function but unfortunately has 2 disadvantages:
Performance is poor, especially in big company's with many users and groups. It fetches a lot more data then you actually need and does a server call for each loop iteration in the result
It contains bugs which can cause your application to stop working 'some day' when groups and users are evolving. Microsoft recognized the issue and is related with some SID's. The error you'll get is "An error occurred while enumerating the groups"
Therefore, I've wrote a small function to replace GetAuthorizationGroups() with better performance and error-safe. It does only 1 LDAP call with a query using indexed fields. It can be easily extended if you need more properties than only the group names ("cn" property).
// Usage: GetAdGroupsForUser2("domain\user") or GetAdGroupsForUser2("user","domain")
public static List<string> GetAdGroupsForUser2(string userName, string domainName = null)
{
var result = new List<string>();
if (userName.Contains('\\') || userName.Contains('/'))
{
domainName = userName.Split(new char[] { '\\', '/' })[0];
userName = userName.Split(new char[] { '\\', '/' })[1];
}
using (PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, domainName))
using (UserPrincipal user = UserPrincipal.FindByIdentity(domainContext, userName))
using (var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domainContext.Name)))
{
searcher.Filter = String.Format("(&(objectCategory=group)(member={0}))", user.DistinguishedName);
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("cn");
foreach (SearchResult entry in searcher.FindAll())
if (entry.Properties.Contains("cn"))
result.Add(entry.Properties["cn"][0].ToString());
}
return result;
}
Within the AD every user has a property memberOf. This contains a list of all groups he belongs to.
Here is a little code example:
// (replace "part_of_user_name" with some partial user name existing in your AD)
var userNameContains = "part_of_user_name";
var identity = WindowsIdentity.GetCurrent().User;
var allDomains = Forest.GetCurrentForest().Domains.Cast<Domain>();
var allSearcher = allDomains.Select(domain =>
{
var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domain.Name));
// Apply some filter to focus on only some specfic objects
searcher.Filter = String.Format("(&(&(objectCategory=person)(objectClass=user)(name=*{0}*)))", userNameContains);
return searcher;
});
var directoryEntriesFound = allSearcher
.SelectMany(searcher => searcher.FindAll()
.Cast<SearchResult>()
.Select(result => result.GetDirectoryEntry()));
var memberOf = directoryEntriesFound.Select(entry =>
{
using (entry)
{
return new
{
Name = entry.Name,
GroupName = ((object[])entry.Properties["MemberOf"].Value).Select(obj => obj.ToString())
};
}
});
foreach (var item in memberOf)
{
Debug.Print("Name = " + item.Name);
Debug.Print("Member of:");
foreach (var groupName in item.GroupName)
{
Debug.Print(" " + groupName);
}
Debug.Print(String.Empty);
}
}
My solution:
UserPrincipal user = UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain, myDomain), IdentityType.SamAccountName, myUser);
List<string> UserADGroups = new List<string>();
foreach (GroupPrincipal group in user.GetGroups())
{
UserADGroups.Add(group.ToString());
}
In my case the only way I could keep using GetGroups() without any expcetion was adding the user (USER_WITH_PERMISSION) to the group which has permission to read the AD (Active Directory). It's extremely essential to construct the PrincipalContext passing this user and password.
var pc = new PrincipalContext(ContextType.Domain, domain, "USER_WITH_PERMISSION", "PASS");
var user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);
var groups = user.GetGroups();
Steps you may follow inside Active Directory to get it working:
Into Active Directory create a group (or take one) and under secutiry tab add "Windows Authorization Access Group"
Click on "Advanced" button
Select "Windows Authorization Access Group" and click on "View"
Check "Read tokenGroupsGlobalAndUniversal"
Locate the desired user and add to the group you created (taken) from the first step
The answer depends on what kind of groups you want to retrieve. The System.DirectoryServices.AccountManagement namespace provides two group retrieval methods:
GetGroups - Returns a collection of group objects that specify the groups of which the current principal is a member.
This overloaded method only returns the groups of which the principal is directly a member; no recursive searches are performed.
GetAuthorizationGroups - Returns a collection of principal objects that contains all the authorization groups of which this user is a member. This function only returns groups that are security groups; distribution groups are not returned.
This method searches all groups recursively and returns the groups in which the user is a member. The returned set may also include additional groups that system would consider the user a member of for authorization purposes.
So GetGroups gets all groups of which the user is a direct member, and GetAuthorizationGroups gets all authorization groups of which the user is a direct or indirect member.
Despite the way they are named, one is not a subset of the other. There may be groups returned by GetGroups not returned by GetAuthorizationGroups, and vice versa.
Here's a usage example:
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "MyDomain", "OU=AllUsers,DC=MyDomain,DC=Local");
UserPrincipal inputUser = new UserPrincipal(domainContext);
inputUser.SamAccountName = "bsmith";
PrincipalSearcher adSearcher = new PrincipalSearcher(inputUser);
inputUser = (UserPrincipal)adSearcher.FindAll().ElementAt(0);
var userGroups = inputUser.GetGroups();
This works for me
public string[] GetGroupNames(string domainName, string userName)
{
List<string> result = new List<string>();
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, domainName))
{
using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(principalContext, userName).GetGroups())
{
src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
}
}
return result.ToArray();
}
In case Translate works locally but not remotly e.i group.Translate(typeof(NTAccount)
If you want to have the application code executes using the LOGGED IN USER identity, then enable impersonation. Impersonation can be enabled thru IIS or by adding the following element in the web.config.
<system.web>
<identity impersonate="true"/>
If impersonation is enabled, the application executes using the permissions found in your user account. So if the logged in user has access, to a specific network resource, only then will he be able to access that resource thru the application.
Thank PRAGIM tech for this information from his diligent video
Windows authentication in asp.net Part 87:
https://www.youtube.com/watch?v=zftmaZ3ySMc
But impersonation creates a lot of overhead on the server
The best solution to allow users of certain network groups is to deny anonymous in the web config
<authorization><deny users="?"/><authentication mode="Windows"/>
and in your code behind, preferably in the global.asax, use the HttpContext.Current.User.IsInRole :
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
If HttpContext.Current.User.IsInRole("TheDomain\TheGroup") Then
//code to do when user is in group
End If
NOTE: The Group must be written with a backslash \ i.e. "TheDomain\TheGroup"
This is quick and dirty but someone may find it helpful. You will need to add the reference to System.DirectoryServices.AccountManagement for this to work. This is just for getting user roles but can be expanded to include other things if needed.
using System.DirectoryServices.AccountManagement;
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "DaomainName");
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, "Username");
List<UserRole> UserRoles = u.GetGroups().Select(x => new UserRole { Role = x.Name }).ToList();
public partial class UserRole
{
public string Role { get; set; }
}

How can I retrieve Active Directory users by Common Name more quickly?

I am querying information from Active Directory. I have code that works, but it's really slow.
This is the code I currently use:
static void Main(string[] args)
{
SearchResultCollection sResults = null;
try
{
//modify this line to include your domain name
string path = "LDAP://EXTECH";
//init a directory entry
DirectoryEntry dEntry = new DirectoryEntry(path);
//init a directory searcher
DirectorySearcher dSearcher = new DirectorySearcher(dEntry);
//This line applies a filter to the search specifying a username to search for
//modify this line to specify a user name. if you want to search for all
//users who start with k - set SearchString to "k"
dSearcher.Filter = "(&(objectClass=user))";
//perform search on active directory
sResults = dSearcher.FindAll();
//loop through results of search
foreach (SearchResult searchResult in sResults)
{
if (searchResult.Properties["CN"][0].ToString() == "Adit")
{
////loop through the ad properties
//foreach (string propertyKey in
//searchResult.Properties["st"])
//{
//pull the collection of objects with this key name
ResultPropertyValueCollection valueCollection =
searchResult.Properties["manager"];
foreach (Object propertyValue in valueCollection)
{
//loop through the values that have a specific name
//an example of a property that would have multiple
//collections for the same name would be memberof
//Console.WriteLine("Property Name: " + valueCollection..ToString());
Console.WriteLine("Property Value: " + (string)propertyValue.ToString());
//["sAMAccountName"][0].ToString();
}
//}
Console.WriteLine(" ");
}
}
}
catch (InvalidOperationException iOe)
{
//
}
catch (NotSupportedException nSe)
{
//
}
finally
{
// dispose of objects used
if (sResults != null)
sResults.Dispose();
}
Console.ReadLine();
}
What would faster code look like to get user information from AD?
You can call UserPrincipal.FindByIdentity inside System.DirectoryServices.AccountManagement:
using System.DirectoryServices.AccountManagement;
using (var pc = new PrincipalContext(ContextType.Domain, "MyDomainName"))
{
var user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, "MyDomainName\\" + userName);
}
The reason why your code is slow is that your LDAP query retrieves every single user object in your domain even though you're only interested in one user with a common name of "Adit":
dSearcher.Filter = "(&(objectClass=user))";
So to optimize, you need to narrow your LDAP query to just the user you are interested in. Try something like:
dSearcher.Filter = "(&(objectClass=user)(cn=Adit))";
In addition, don't forget to dispose these objects when done:
DirectoryEntry dEntry
DirectorySearcher dSearcher
Well, if you know where your user lives in the AD hierarchy (e.g. quite possibly in the "Users" container, if it's a small network), you could also bind to the user account directly, instead of searching for it.
DirectoryEntry deUser = new DirectoryEntry("LDAP://cn=John Doe,cn=Users,dc=yourdomain,dc=com");
if (deUser != null)
{
... do something with your user
}
And if you're on .NET 3.5 already, you could even use the vastly expanded System.DirectorySrevices.AccountManagement namespace with strongly typed classes for each of the most common AD objects:
// bind to your domain
PrincipalContext pc = new PrincipalContext(ContextType.Domain, "LDAP://dc=yourdomain,dc=com");
// find the user by identity (or many other ways)
UserPrincipal user = UserPrincipal.FindByIdentity(pc, "cn=John Doe");
There's loads of information out there on System.DirectoryServices.AccountManagement - check out this excellent article on MSDN by Joe Kaplan and Ethan Wilansky on the topic.
You can simplify this code to:
DirectorySearcher searcher = new DirectorySearcher();
searcher.Filter = "(&(objectCategory=user)(cn=steve.evans))";
SearchResultCollection results = searcher.FindAll();
if (results.Count == 1)
{
//do what you want to do
}
else if (results.Count == 0)
{
//user does not exist
}
else
{
//found more than one user
//something is wrong
}
If you can narrow down where the user is you can set searcher.SearchRoot to a specific OU that you know the user is under.
You should also use objectCategory instead of objectClass since objectCategory is indexed by default.
You should also consider searching on an attribute other than CN. For example it might make more sense to search on the username (sAMAccountName) since it's guaranteed to be unique.
I'm not sure how much of your "slowness" will be due to the loop you're doing to find entries with particular attribute values, but you can remove this loop by being more specific with your filter. Try this page for some guidance ... Search Filter Syntax

Categories