I'm using C# code to query an Active Directory. The main issue I'm having is determining whether an account has been disabled or not. Looking through many online articles, it would appear that one cannot solely rely on the property UserPrincipal.Enabled to determine if a user account is enabled or not. Fair enough, as its a nullable Boolean but when an AD administrator disables an account, this does appear to get set to false. The problem I have is when I query a client's AD, I find that most user accounts UserPrincipal objects return false for this property. So when I use this code to check if an account is disabled:
private bool IsUserEnabled(UserPrincipal userPrincipal)
{
bool isEnabled = true;
if (userPrincipal.AccountExpirationDate != null)
{
// Check the expiration date is not passed.
if (userPrincipal.AccountExpirationDate <= DateTime.Now)
{
Log.DebugFormat("User {0} account has expired on {1}", userPrincipal.DisplayName, userPrincipal.AccountExpirationDate.Value);
isEnabled = false;
}
}
if (userPrincipal.IsAccountLockedOut())
{
isEnabled = false;
Log.DebugFormat("User {0} account is locked out", userPrincipal.DisplayName);
}
if (userPrincipal.Enabled != null)
{
isEnabled = userPrincipal.Enabled.Value;
Log.DebugFormat("User {0} account is Enabled is set to {1}", userPrincipal.DisplayName, userPrincipal.Enabled.Value);
}
return isEnabled;
}
Most accounts appear disabled because of the userPrincipal.Enabled check.
However, if I leave this out and just rely on the account expiration date and the account lockout properties, then I may miss someone who is disabled using the checkbox in Active Directory which simply disables the account - without setting the account expiration date.
All the accounts where enabled returns false are actually active accounts who can log in to the domain.
How do you check if an account is actually enabled or not?
I ran into a similar issue, and was equally perplexed!
I initially was using a System.DirectoryServices.DirectorySearcher to search for disabled users. The status of an AD user record (re: disabled, locked out, password expiry, etc) is stored within the UserAccountControl property. You could pass in a filter to the DirectorySearcher to locate, lets says, disabled accounts by specifying the UserAccountControl property as part of the filter.
I was never fond of this approach as it amounted to using a magic string and some magic numbers in order to build the query; for example, this is the filter used to locate disabled accounts:
var searcher = new DirectorySearcher(dirEntry)
{
Filter = "(UserAccountControl:1.2.840.113556.1.4.803:=2)",
PageSize = 50
};
When I switched over to using the UserPrincipal, I was thrilled to see this nice handy "Enabled" property right on the class.. At least until I realized it didn't return the same value that the DirectorySearcher filter would return.
Unfortunately, the only reliable way that I could find to determine if the account was actually enabled was to dig into the underlying DirectoryEntry object, and go inspect the UserAccountControl property directly, ie:
var result = (DirectoryEntry)userPrincipal.GetUnderlyingObject();
var uac = (int)result.Properties["useraccountcontrol"].Value;
var isEnabled = !Convert.ToBoolean(uac & 2);
Note - the UserAccountControl property is a "flags" enum; all possible values for the UserAccountControl property can be found here: https://msdn.microsoft.com/en-us/library/aa772300(v=vs.85).aspx
I ended up building the above snippet into a little extension method; fortunately doing this extra work to retrieve the UserAccountControl property didn't noticeable slow down my AD queries.
I got a solution by expanding the result and going to the base and expanding the base. You will see the enabled property there. Then I right click the expression and add to watch and copy the watch expression to my code.
using (var context = new PrincipalContext(ContextType.Domain, "xyz.com", "Administrator", "xyz123"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DrectoryEntry;
foreach (String key in de.Properties.PropertyNames)
{
Console.WriteLine(key + " : " + de.Properties[key].Value);
}
Console.WriteLine("Enabled: " +((System.DirectoryServices.AccountManagement.AuthenticablePrincipal)(result)).Enabled);
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
}
}
}
Console.ReadLine();
((System.DirectoryServices.AccountManagement.AuthenticablePrincipal)(result)).Enabled
is working fine to get the enabled true or false in the list of users.
I can tell you what works for me in one of my applications. Here is a snippet from my application:
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "domain.com"))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(pc, "Doe, John"))
{
Console.Out.Write(user.Enabled);
}
}
This, for me, accurately returns whether the account is enabled or not.
Related
How can I get a list of users from active directory? Is there a way to pull username, firstname, lastname? I saw a similar post where this was used:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
I have never done anything with active directory so I am completely lost. Any help would be greatly appreciated!
If you are new to Active Directory, I suggest you should understand how Active Directory stores data first.
Active Directory is actually a LDAP server. Objects stored in LDAP server are stored hierarchically. It's very similar to you store your files in your file system. That's why it got the name Directory server and Active Directory
The containers and objects on Active Directory can be specified by a distinguished name. The distinguished name is like this CN=SomeName,CN=SomeDirectory,DC=yourdomain,DC=com. Like a traditional relational database, you can run query against a LDAP server. It's called LDAP query.
There are a number of ways to run a LDAP query in .NET. You can use DirectorySearcher from System.DirectoryServices or SearchRequest from System.DirectoryServices.Protocol.
For your question, since you are asking to find user principal object specifically, I think the most intuitive way is to use PrincipalSearcher from System.DirectoryServices.AccountManagement. You can easily find a lot of different examples from google. Here is a sample that is doing exactly what you are asking for.
using (var context = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
Console.WriteLine("Last Name : " + de.Properties["sn"].Value);
Console.WriteLine("SAM account name : " + de.Properties["samAccountName"].Value);
Console.WriteLine("User principal name: " + de.Properties["userPrincipalName"].Value);
Console.WriteLine();
}
}
}
Console.ReadLine();
Note that on the AD user object, there are a number of attributes. In particular, givenName will give you the First Name and sn will give you the Last Name. About the user name. I think you meant the user logon name. Note that there are two logon names on AD user object. One is samAccountName, which is also known as pre-Windows 2000 user logon name. userPrincipalName is generally used after Windows 2000.
If you want to filter y active accounts add this to Harvey's code:
UserPrincipal userPrin = new UserPrincipal(context);
userPrin.Enabled = true;
after the first using. Then add
searcher.QueryFilter = userPrin;
before the find all. And that should get you the active ones.
PrincipalContext for browsing the AD is ridiculously slow (only use it for .ValidateCredentials, see below), use DirectoryEntry instead and .PropertiesToLoad() so you only pay for what you need.
Filters and syntax here:
https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
Attributes here:
https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all
using (var root = new DirectoryEntry($"LDAP://{Domain}"))
{
using (var searcher = new DirectorySearcher(root))
{
// looking for a specific user
searcher.Filter = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={username}))";
// I only care about what groups the user is a memberOf
searcher.PropertiesToLoad.Add("memberOf");
// FYI, non-null results means the user was found
var results = searcher.FindOne();
var properties = results?.Properties;
if (properties?.Contains("memberOf") == true)
{
// ... iterate over all the groups the user is a member of
}
}
}
Clean, simple, fast. No magic, no half-documented calls to .RefreshCache to grab the tokenGroups or to .Bind or .NativeObject in a try/catch to validate credentials.
For authenticating the user:
using (var context = new PrincipalContext(ContextType.Domain))
{
return context.ValidateCredentials(username, password);
}
Certainly the credit goes to #Harvey Kwok here, but I just wanted to add this example because in my case I wanted to get an actual List of UserPrincipals. It's probably more efficient to filter this query upfront, but in my small environment, it's just easier to pull everything and then filter as needed later from my list.
Depending on what you need, you may not need to cast to DirectoryEntry, but some properties are not available from UserPrincipal.
using (var searcher = new PrincipalSearcher(new UserPrincipal(new PrincipalContext(ContextType.Domain, Environment.UserDomainName))))
{
List<UserPrincipal> users = searcher.FindAll().Select(u => (UserPrincipal)u).ToList();
foreach(var u in users)
{
DirectoryEntry d = (DirectoryEntry)u.GetUnderlyingObject();
Console.WriteLine(d.Properties["GivenName"]?.Value?.ToString() + d.Properties["sn"]?.Value?.ToString());
}
}
Include the System.DirectoryServices.dll, then use the code below:
DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" + Environment.MachineName);
string userNames="Users: ";
foreach (DirectoryEntry child in directoryEntry.Children)
{
if (child.SchemaClassName == "User")
{
userNames += child.Name + Environment.NewLine ;
}
}
MessageBox.Show(userNames);
I'm trying to search for users in AD and display them in a list box only when they are inactive.
This is the code I have written
private void button1_Click(object sender, EventArgs e)
{
PrincipalContext insPrincipalContext = new PrincipalContext(ContextType.Domain, "DX", "DC=RX,DC=PX,DC=com");
ListUser(insPrincipalContext);
}
private void ListUser(PrincipalContext insPrincipalContext)
{
UserPrincipal insUserPrincipal = new UserPrincipal(insPrincipalContext);
insUserPrincipal.Name = "*";
SearchUsers(insUserPrincipal);
}
private void SearchUsers(UserPrincipal parUserPrincipal)
{
listBox1.Items.Clear();
PrincipalSearcher insPrincipalSearcher = new PrincipalSearcher();
insPrincipalSearcher.QueryFilter = parUserPrincipal;
PrincipalSearchResult<Principal> results = insPrincipalSearcher.FindAll();
foreach (Principal p in results)
{
UserPrincipal theUser = p as UserPrincipal;
if (theUser != null)
{
if (***theUser.IsAccountLockedOut()***)//**Is this same as Active or Inactive?**
{
listBox1.Items.Add(p);
}
else
{
}
}
}
}
So my question is whether (theUser.)IsAccountLockedUp is same as asking if the user is inactive?
I know one might suggest that this question is a copy of Get members of Active Directory Group and check if they are enabled or disabled but the problem here is I don't have test users to test on and I'm just starting with C#.
Thank You
IsAccountLockedOut corresponds to "Account is locked out" in the Active Directory account properties. This means that account was locked due to too many bad password attempts.
There is another setting in the properties "Account is disabled". This is often used by Administrators (of Identity Management Systems in large enterprise environments) to disable a account if the corresponding person left the company. So the account cannot be used anymore, but it is still there and works for SID lookup (will be displayed as name in groups or ACLs).
Ask yourself what your intention is. What do mean by "inactive" ?
You can probably use this as a starting point:
if (theUser != null)
{
if (theUser.IsAccountLockedOut())
{
// account locked out
}
// Enabled is nullable bool
if (!(theUser.Enabled == true))
{
// account disabled
}
if (theUser.LastLogon == null ||
theUser.LastLogon < DateTime.Now - TimeSpan.FromDays(150))
{
// never logged on or last logged on long time ago
// see below in the text for important notes !
}
}
Checking for LastLogon can be useful to find orphaned accounts. But be aware that a women may be in maternity leave :-)
Important
In Active Directory, there are two similar attributes:
LastLogon
LastLogonTimeStamp
See
http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx
for the difference. Short: only the LastLogonTimeStamp attribute is replicated between domain controllers and can be safely used for checking orphaned accounts. But even LastLogonTimeStamp is not really accurate, but is sufficient for detecting "old" / unused accounts.
I have not yet checked to which of them UserPrinciple.LastLogon corresponds. Be careful.
I am in the process of writing an own library for searching in the AD, which is based on the System.DirectoryServices classes. This has some benefits: better control and better performance. If it is ready, I will probably make it public.
I am writing a standalone application that needs to, given a AD account name, figure out if the user is a member of a particular group. I am writing the application in .NET (C#). The structure in AD looks like this:
Organization
Team 1
David (user)
Groups
Application A
Team 1 (group from above)
Just listing the memberships for David will not show that he is a member of the Application A group.
I understand from Microsoft's documentation that (using the principals) I could simply use the IsInRole call, but I cannot find any case that doesn't require David to be logged on to the machine or running the application performing the check. I think my limited understanding of the security model also comes into play here.
Could someone please point me in the right direction? What I am looking for is hints on how to solve above (references, tips, snippets) in C#, without depending on David having to run any application.
Let me know if anything can be clarified.
Add a reference to DirectoryServices.AccountManagement
Then add a using statement:
using System.DirectoryServices.AccountManagement;
Then in your main procedure (or somewhere else, where required, call the Procedure IsMember:
string userName = "David";
string GroupName = "Team 1";
bool test = IsMember(userName, GroupName);
public static bool IsMember(string UserName, string GroupName)
{
try
{
UserPrincipal user = UserPrincipal.FindByIdentity(
new PrincipalContext(ContextType.Domain),
UserName);
foreach (Principal result in user.GetAuthorizationGroups())
{
if (string.Compare(result.Name, GroupName, true) == 0)
return true;
}
return false;
}
catch (Exception E)
{
throw E;
}
}
If David is in Team 1, the procedure will return true, otherwise false.
You can use UserPrincipal.FindByIdentity to obtain a UserPrincipal object from the directory. This isn't exactly like the other principal objects you may have found, but it does have an IsMemberOf method to allow you to query group membership.
I use this in my AD environment
var pc = new PrincipalContext(ContextType.Domain);
var group = GroupPrincipal.FindByIdentity(pc, "GROUPNAME");
var existsInGroup = group.GetMembers(true).Where(p => p.UserPrincipalName == "username#domain").Any();
If you don't want to check subgroups, pass false to GetMembers.
It doesn't require given user has to be logged on. Hope it helps.
How can I get a list of users from active directory? Is there a way to pull username, firstname, lastname? I saw a similar post where this was used:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
I have never done anything with active directory so I am completely lost. Any help would be greatly appreciated!
If you are new to Active Directory, I suggest you should understand how Active Directory stores data first.
Active Directory is actually a LDAP server. Objects stored in LDAP server are stored hierarchically. It's very similar to you store your files in your file system. That's why it got the name Directory server and Active Directory
The containers and objects on Active Directory can be specified by a distinguished name. The distinguished name is like this CN=SomeName,CN=SomeDirectory,DC=yourdomain,DC=com. Like a traditional relational database, you can run query against a LDAP server. It's called LDAP query.
There are a number of ways to run a LDAP query in .NET. You can use DirectorySearcher from System.DirectoryServices or SearchRequest from System.DirectoryServices.Protocol.
For your question, since you are asking to find user principal object specifically, I think the most intuitive way is to use PrincipalSearcher from System.DirectoryServices.AccountManagement. You can easily find a lot of different examples from google. Here is a sample that is doing exactly what you are asking for.
using (var context = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
Console.WriteLine("Last Name : " + de.Properties["sn"].Value);
Console.WriteLine("SAM account name : " + de.Properties["samAccountName"].Value);
Console.WriteLine("User principal name: " + de.Properties["userPrincipalName"].Value);
Console.WriteLine();
}
}
}
Console.ReadLine();
Note that on the AD user object, there are a number of attributes. In particular, givenName will give you the First Name and sn will give you the Last Name. About the user name. I think you meant the user logon name. Note that there are two logon names on AD user object. One is samAccountName, which is also known as pre-Windows 2000 user logon name. userPrincipalName is generally used after Windows 2000.
If you want to filter y active accounts add this to Harvey's code:
UserPrincipal userPrin = new UserPrincipal(context);
userPrin.Enabled = true;
after the first using. Then add
searcher.QueryFilter = userPrin;
before the find all. And that should get you the active ones.
PrincipalContext for browsing the AD is ridiculously slow (only use it for .ValidateCredentials, see below), use DirectoryEntry instead and .PropertiesToLoad() so you only pay for what you need.
Filters and syntax here:
https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
Attributes here:
https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all
using (var root = new DirectoryEntry($"LDAP://{Domain}"))
{
using (var searcher = new DirectorySearcher(root))
{
// looking for a specific user
searcher.Filter = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={username}))";
// I only care about what groups the user is a memberOf
searcher.PropertiesToLoad.Add("memberOf");
// FYI, non-null results means the user was found
var results = searcher.FindOne();
var properties = results?.Properties;
if (properties?.Contains("memberOf") == true)
{
// ... iterate over all the groups the user is a member of
}
}
}
Clean, simple, fast. No magic, no half-documented calls to .RefreshCache to grab the tokenGroups or to .Bind or .NativeObject in a try/catch to validate credentials.
For authenticating the user:
using (var context = new PrincipalContext(ContextType.Domain))
{
return context.ValidateCredentials(username, password);
}
Certainly the credit goes to #Harvey Kwok here, but I just wanted to add this example because in my case I wanted to get an actual List of UserPrincipals. It's probably more efficient to filter this query upfront, but in my small environment, it's just easier to pull everything and then filter as needed later from my list.
Depending on what you need, you may not need to cast to DirectoryEntry, but some properties are not available from UserPrincipal.
using (var searcher = new PrincipalSearcher(new UserPrincipal(new PrincipalContext(ContextType.Domain, Environment.UserDomainName))))
{
List<UserPrincipal> users = searcher.FindAll().Select(u => (UserPrincipal)u).ToList();
foreach(var u in users)
{
DirectoryEntry d = (DirectoryEntry)u.GetUnderlyingObject();
Console.WriteLine(d.Properties["GivenName"]?.Value?.ToString() + d.Properties["sn"]?.Value?.ToString());
}
}
Include the System.DirectoryServices.dll, then use the code below:
DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" + Environment.MachineName);
string userNames="Users: ";
foreach (DirectoryEntry child in directoryEntry.Children)
{
if (child.SchemaClassName == "User")
{
userNames += child.Name + Environment.NewLine ;
}
}
MessageBox.Show(userNames);
I need to create a new user in Active Directory. I have found several examples like the following:
using System;
using System.DirectoryServices;
namespace test {
class Program {
static void Main(string[] args) {
try {
string path = "LDAP://OU=x,DC=y,DC=com";
string username = "johndoe";
using (DirectoryEntry ou = new DirectoryEntry(path)) {
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Add(username);
ou.CommitChanges();
}
}
catch (Exception exc) {
Console.WriteLine(exc.Message);
}
}
}
}
When I run this code I get no errors, but no new user is created.
The account I'm running the test with has sufficient privileges to create a user in the target Organizational Unit.
Am I missing something (possibly some required attribute of the user object)?
Any ideas why the code does not give exceptions?
EDIT
The following worked for me:
int NORMAL_ACCOUNT = 0x200;
int PWD_NOTREQD = 0x20;
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Value = username;
user.Properties["userAccountControl"].Value = NORMAL_ACCOUNT | PWD_NOTREQD;
user.CommitChanges();
So there were actually a couple of problems:
CommitChanges must be called on user (thanks Rob)
The password policy was preventing the user to be created (thanks Marc)
I think you are calling CommitChanges on the wrong DirectoryEntry. In the MSDN documentation (http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentries.add.aspx) it states the following (emphasis added by me)
You must call the CommitChanges method on the new entry to make the creation permanent. When you call this method, you can then set mandatory property values on the new entry. The providers each have different requirements for properties that need to be set before a call to the CommitChanges method is made. If those requirements are not met, the provider might throw an exception. Check with your provider to determine which properties must be set before committing changes.
So if you change your code to user.CommitChanges() it should work, if you need to set more properties than just the account name then you should get an exception.
Since you're currently calling CommitChanges() on the OU which hasn't been altered there will be no exceptions.
Assuming your OU path OU=x,DC=y,DC=com really exists - it should work :-)
Things to check:
you're adding a value to the "samAccountName" - why don't you just set its value:
user.Properties["sAMAccountName"].Value = username;
Otherwise you might end up with several samAccountNames - and that won't work.....
you're not setting the userAccountControl property to anything - try using:
user.Properties["userAccountControl"].Value = 512; // normal account
do you have multiple domain controllers in your org? If you, and you're using this "server-less" binding (not specifying any server in the LDAP path), you could be surprised where the user gets created :-) and it'll take several minutes up to half an hour to synchronize across the whole network
do you have a strict password policy in place? Maybe that's the problem. I recall we used to have to create the user with the "doesn't require password" option first, do a first .CommitChanges(), then create a powerful enough password, set it on the user, and remove that user option.
Marc
Check the below code
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 3; i < 6; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}