I am using the System.DirectoryServices.AccountManagement part of the .Net library to interface into ActiveDirectory.
Having called GetMembers() on a GroupPrincipal object and filter the results, I now have a collection of UserPrincipal objects
GroupPrincipal myGroup; // population of this object omitted here
foreach (UserPrincipal user in myGroup.GetMembers(false).OfType<UserPrincipal>())
{
Console.WriteLine(user.SamAccountName);
}
The above code sample will print out usernames like "TestUser1". I need to compare these to a list coming from another application in "DOMAIN\TestUser1" format.
How do I get the "DOMAIN" part from the UserPrincipal object?
I can't just append a known domain name as there are multiple domains involved and I need to differentiate DOMAIN1\TestUser1 and DOMAIN2\TestUser2.
You have two choices that I can think of.
Parse, or take everything that is on, the right of name#fully.qualified.domain.name;
Use the System.DirectoryServices namespace.
I don't know about UserPrincipal, neither do I about GroupPrincipal. On the other hand, I know of a working way to achive to what you want.
[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("INTRA\TESTUSER1", string.Concat(netBiosName, "\", foundLogin).ToUpperInvariant())
}
Other related information or links available in this SO question.
C# Active Directory: Get domain name of user?
How to find the NetBIOS name of a domain
Use the ActiveDs COM library, it has built-in name translation that works and does not make any assumptions (like other answers here).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ActiveDs;
namespace Foo.Repository.AdUserProfile
{
public class ADUserProfileValueTranslate
{
public static string ConvertUserPrincipalNameToNetBiosName(string userPrincipleName)
{
NameTranslate nameTranslate = new NameTranslate();
nameTranslate.Set((int)ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_USER_PRINCIPAL_NAME, userPrincipleName);
return nameTranslate.Get((int) ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_NT4);
}
}
}
You could look for the possible domains in the user.DistinguishedName property.
A user in Domain 1 should contain the string "DC=DOMAIN1". It definitely shouldn't contain the string "DC=DOMAIN2".
As mentioned in one of the comments to the question I think this is a good answer for more recent times:
user.Sid.Translate(typeof(System.Security.Principal.NTAccount)).ToString()
Have you tried passing the fully qualified domain name to this other app? Most windows API's won't complain if you do fully_qualified_domain\USER.
Related
I am making an Active Directry managment tool but I am having trouble getting somethings working.
While I made a class where I want to find a specific user and return als his information(Name,Fullname,CN,...). I can find all this information but when I don't know the best way to return all the values from my class.
Here is the code I use so far:
DirectorySearcher search = new DirectorySearcher(ldapConnectie);
search.Filter = "(cn=" + username + ")";
SearchResult result = search.FindOne();
if (result != null)
{
List<string> listLdapFields = new List<string>();
List<Object> listLdapValues = new List<Object>();
ResultPropertyCollection fields = result.Properties;
foreach (String ldapField in fields.PropertyNames)
{
listLdapFields.Add(ldapField);
foreach (Object myCollection in fields[ldapField])
{
listLdapValues.Add(myCollection);
}
}
}
The program add everything well to a list. But if I return this I cannot search the list on "CN" or "Name". I can only find the information on Index Number.
Hope you can help me out.
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, username);
if(user != null)
{
// do something here....
// the most often used attributes are available as nice, strongly-typed properties
string value = user.GivenName;
value = user.Surname;
value = user.EmailAddress;
value = user.VoiceTelephoneNumber;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
Here's an approach where you can call the property by name. It also uses the authenticated account making the request.
string domainPath = String.Format("LDAP://{0},DC=site,DC=com", domain);
using (DirectoryEntry searchRoot = new DirectoryEntry(domainPath))
{
using (DirectorySearcher search =
filterSearch(new DirectorySearcher(searchRoot), username))
{
SearchResult result = null;
try
{
result = search.FindOne();
}
catch (DirectoryServicesCOMException e)
{
//handle the error
}
if (result != null)
{
string givenname = result.Properties["givenname"].Count > 0 ?
(string)result.Properties["givenname"][0] : "";
string sn = result.Properties["sn"].Count > 0 ?
(string)result.Properties["sn"][0] : "";
var samaccount= result.Properties["samaccountname"].Count > 0 ?
(string)result.Properties["samaccountname"][0] : "";
var name = String.Format("{0}, {1}", sn, givenname);
var email = result.Properties["mail"].Count > 0 ?
(string)result.Properties["mail"][0] : "";
}
}
}
//Apply a filter to search only specific classes and categories.
//Add the specific properties to be retrieved
private DirectorySearcher filterSearch(DirectorySearcher search, string username)
{
DirectorySearcher filteredSearch = search;
filteredSearch.Filter = "(&(objectClass=user)(objectCategory=person)(samaccountname=" + username + "))";
filteredSearch.PropertiesToLoad.Add("givenname");
filteredSearch.PropertiesToLoad.Add("sn");
filteredSearch.PropertiesToLoad.Add("samaccountname");
filteredSearch.PropertiesToLoad.Add("department");
filteredSearch.PropertiesToLoad.Add("physicalDeliveryOfficeName");
filteredSearch.PropertiesToLoad.Add("mail");
return filteredSearch;
}
This may be helpful if looking for specific properties, but if you want to retrieve a list of all attributes/values, take a look at this other SO question.
Google has a list of common filters you can use. Take a look through them and modify the filterSearch method appropriately.
I have an application that uses ActiveDirecotry authorisation and it has been decided that it needs to support nested AD groups, e.g.:
MAIN_AD_GROUP
|
|-> SUB_GROUP
|
|-> User
So, the user in not directly a member of MAIN_AD_GROUP. I'd like to be able to look for the user recursively, searching the groups nested in MAIN_AD_GROUP.
The main problem is that I'm using .NET 3.5 and there is a bug in System.DirectoryServices.AccountManagement in .NET 3.5 whereby the method UserPrincipal.IsMemberOf() will not work for groups with more than 1500 users. So I can't use UserPrincipal.IsMemberOf() and no, I can't switch to .NET 4 either.
I've worked around this last problem with the following function:
private bool IsMember(Principal userPrincipal, Principal groupPrincipal)
{
using (var groups = userPrincipal.GetGroups())
{
var isMember = groups.Any(g =>
g.DistinguishedName == groupPrincipal.DistinguishedName);
return isMember;
}
}
But userPrincipal.GetGroups() only returns the groups of which the user is a direct member.
How can I get this to work with nested groups?
Workaround #1
This bug is reported here at Microsoft Connect along with the following code that works around this issue by manually iterating through the PrincipalSearchResult<Principal> returned objects, catching this exception, and continuing on:
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
var iterGroup = groups.GetEnumerator();
using (iterGroup)
{
while (iterGroup.MoveNext())
{
try
{
Principal p = iterGroup.Current;
Console.WriteLine(p.Name);
}
catch (NoMatchingPrincipalException pex)
{
continue;
}
}
}
Workaround #2
Another workaround found here avoids the AccountManagement class, and uses the System.DirectoryServices API instead:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
namespace GetGroupsForADUser
{
class Program
{
static void Main(string[] args)
{
String username = "Gabriel";
List<string> userNestedMembership = new List<string>();
DirectoryEntry domainConnection = new DirectoryEntry(); // Use this to query the default domain
//DirectoryEntry domainConnection = new DirectoryEntry("LDAP://example.com", "username", "password"); // Use this to query a remote domain
DirectorySearcher samSearcher = new DirectorySearcher();
samSearcher.SearchRoot = domainConnection;
samSearcher.Filter = "(samAccountName=" + username + ")";
samSearcher.PropertiesToLoad.Add("displayName");
SearchResult samResult = samSearcher.FindOne();
if (samResult != null)
{
DirectoryEntry theUser = samResult.GetDirectoryEntry();
theUser.RefreshCache(new string[] { "tokenGroups" });
foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])
{
System.Security.Principal.SecurityIdentifier mySID = new System.Security.Principal.SecurityIdentifier(resultBytes, 0);
DirectorySearcher sidSearcher = new DirectorySearcher();
sidSearcher.SearchRoot = domainConnection;
sidSearcher.Filter = "(objectSid=" + mySID.Value + ")";
sidSearcher.PropertiesToLoad.Add("distinguishedName");
SearchResult sidResult = sidSearcher.FindOne();
if (sidResult != null)
{
userNestedMembership.Add((string)sidResult.Properties["distinguishedName"][0]);
}
}
foreach (string myEntry in userNestedMembership)
{
Console.WriteLine(myEntry);
}
}
else
{
Console.WriteLine("The user doesn't exist");
}
Console.ReadKey();
}
}
}
Use UserPrincipal.GetAuthorizationGroups() instead - from its MSDN docs:
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.
The groups that are returned by this
method may include groups from a
different scope and store than the
principal. For example, if the
principal is an AD DS object that has
a DN of
"CN=SpecialGroups,DC=Fabrikam,DC=com,
the returned set can contain groups
that belong to the
"CN=NormalGroups,DC=Fabrikam,DC=com.
I know this is an old thread, but it's the top result on Google, so in case this helps anyone, here's what I came up with that uses the AccountManagement stuff, but makes this particular query much easier.
public static class AccountManagementExtensions
{
public static bool IsNestedMemberOf(this Principal principal, GroupPrincipal group)
{
// LDAP Query for memberOf Nested
var filter = String.Format("(&(sAMAccountName={0})(memberOf:1.2.840.113556.1.4.1941:={1}))",
principal.SamAccountName,
group.DistinguishedName
);
var searcher = new DirectorySearcher(filter);
var result = searcher.FindOne();
return result != null;
}
}
The efficient way is to do a single AD query by having the right DirectorySearcher filter for e.g.
public bool CheckMemberShip(string userName)
{
bool membership = false;
string connection = "LDAP://"+YOURDOMAIN;
DirectoryEntry entry = new DirectoryEntry(connection);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=cn=GROUPNAME,OU=Groups,OU=ABC,OU=ABC,OU=IND,DC=ad,DC=COMPANY,DC=com)(|(sAMAccountName=" + userName + ")))";
SearchResult result = mySearcher.FindOne();
// No search result, hence no membership
if (result == null)
{
membership = false;
}
entry.Close();
entry.Dispose();
mySearcher.Dispose();
membership = true;
return membership;
}
You need to replace YOURDOMAIN and GROUPNAME with right values from your AD.
Source : How to Recursively Get the Group Membership of a User in Active Directory using .NET/C# and LDAP (without just 2 hits to Active Directory)
Need to include using System.DirectoryServices;
How To Get User group of user from LDAP active directory in C# .NET for ASP. In my Scenario I want to Pass user name to method which query from LDAP Active directory and tell me my user is Member of This User Groups. Please help me in this
If you're on .NET 3.5 or newer, you can also use the new System.DirectoryServices.AccountManagement (S.DS.AM) namespaces.
With this, you can do something like:
// create context for domain
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, "YourUserName");
if(up != null)
{
// get groups for that user
var authGroups = up.GetAuthorizationGroups();
}
Read more about the new S.DS.AM namespace:
Managing Directory Security Principals in the .NET Framework 3.5
Look into using the System.DirectoryServices namespace. You can use a DirectorySearcher to find the user. Once you have the DirectoryEntry object for that user do this:
public List<string> GetMemberOf(DirectoryEntry de)
{
List<string> memberof = new List<string>();
foreach (object oMember in de.Properties["memberOf"])
{
memberof.Add(oMember.ToString());
}
return memberof;
}
This will return a list of strings which are the group names the user is a member of.
Of course you could further refine this to include the DirectorySearcher code so you can just pass the function the samAccountName.
try this...
public override string[] GetRolesForUser(string username)
{
var allRoles = new List<string>();
var root = new DirectoryEntry(WebConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString,
ConnectionUsername,
ConnectionPassword);
var searcher = new DirectorySearcher(root,
string.Format(CultureInfo.InvariantCulture, "(&(objectClass=user)({0}={1}))",
AttributeMapUsername,
username));
searcher.PropertiesToLoad.Add("memberOf");
SearchResult result = searcher.FindOne();
if (result != null && !string.IsNullOrEmpty(result.Path))
{
DirectoryEntry user = result.GetDirectoryEntry();
PropertyValueCollection groups = user.Properties["memberOf"];
foreach (string path in groups)
{
string[] parts = path.Split(',');
if (parts.Length > 0)
{
foreach (string part in parts)
{
string[] p = part.Split('=');
if (p[0].Equals("cn", StringComparison.OrdinalIgnoreCase))
{
allRoles.Add(p[1]);
}
}
}
}
}
return allRoles.ToArray();
}
Use the DirectorySearcher class to preform an ldap query.
For reference:
http://www.codeproject.com/KB/system/QueryADwithDotNet.aspx
I needed a method of authenticating a user and a check to see if they were in a specific user group. I did it by pushing the username and password and loading the "memberOf" property into the 'search' instance. Example below will display all the groups for that specific user name. The 'catch' statement will trap a wrong user name or password.
DirectoryEntry entry = new DirectoryEntry("LDAP://xxxxxxxx/OU=xxxxxxx,DC=xxxxxx,DC=xxxxx,DC=xxxxxx", strLdapUserName, strLdapPassword);
try
{
//the object is needed to fire off the ldap connection
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + strLdapUserName + ")";
search.PropertiesToLoad.Add("memberOf");
SearchResult result = search.FindOne();
string filterAttribute = (String)result.Properties["cn"][0];
foreach(string groupMemberShipName in result.Properties["memberOf"])
{
Console.WriteLine("Member of - {0}", groupMemberShipName);
}
}
catch (Exception ex)
{
//failed to authenticate
throw new Exception(ex.ToString());
}
Hope this helps. (Remember to reference System.DirectoryServices)
I think most methods listed above should work, but i would suggest adding code to ensure that your code can "detect circular loops in nested group memberships", and if found, break any infinite loops that your script of choice could potentially get into.
I am using this simple method of finding a user in the current domain, that works for all users that 'exist' but I can't find any way to determine if the user does not exist.
string userLDAP = #"MYDOMAIN/username";
string path = "WinNT://" + userLDAP ;
DirectoryEntry root = new DirectoryEntry(path, null, null, AuthenticationTypes.Secure);
Other than letting an exception be thrown, how can I use a directory entry to determine if a user does not exist?
if (root.Properties != null)
if (root.Properties["objectSid"] != null) //// EXCEPTION HERE
if (root.Properties["objectSid"][0] != null)
It's better to use DirectorySearcher for this purpose...
string userName = "TargetUserName";
using (DirectorySearcher searcher = new DirectorySearcher("GC://yourdomain.com"))
{
searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", userName);
using (SearchResultCollection results = searcher.FindAll())
{
if (results.Count > 0)
Debug.WriteLine("Found User");
}
}
This sample will search and entire forest including child domains. If you want to target only a single domain use "LDAP://mydomain.com" instead of "GC://mydomain.com". You can also supply searcher.SearchRoot with a DirectoryEntry to use as the root of a search (i.e. a specific OU or domain).
Don't forget most of the AD stuff is IDisposable so dispose properly as shown above.
I think an easy way to check if your DirectoryEntry object points to an existing AD entry is using the static Exists method.
So your code may look like this:
using(DirectoryEntry de = new DirectoryEntry(....)) {
// now we check if the related object exists
bool exists = DirectoryEntry.Exists(de.Path);
if(exists) {
// yes the objects exists
// do something
} // end if
} // end using
Of course you can omit the exists variable. I used it just to make the statement more clear.
The answer to this question on how to check if Windows user account name exists in domain may be helpful to you.
Are you looking for a specific user, or all users?
I have an application that checks if a user is present by checking the account name - it uses SecurityIdentifier in the System.Security.Principal namespace to check if the Sid is valid.
public bool AccountExists(string name)
{
bool SidExists = false;
try
{
NTAccount Acct = new NTAccount(name);
SecurityIdentifier id = (SecurityIdentifier)Acct.Translate(typeof(SecurityIdentifier));
SidExists = id.IsAccountSid();
}
catch (IdentityNotMappedException)
{
//Oh snap.
}
return SidExists;
}
You can specify the Domain when creating your NTAccount object
NTAccount Acct = new NTAccount("SampleDomain", "SampleName");
EDIT
In reference to your comment, would this work for you? Didnt check it, might have to handle a possible null return before evaulating the IsAccountSid() method...
public SecurityIdentifier AccountSID(string myDomain, string myAcct)
{
SecurityIdentifier id;
try
{
NTAccount Acct = new NTAccount(myDomain, myAcct);
id = (SecurityIdentifier)Acct.Translate(typeof(SecurityIdentifier));
}
catch (IdentityNotMappedException)
{
//Oh snap.
}
return id;
}
SecurityIdentifier AcctSID = AccountSID("ExampleDomain", "ExampleName");
if (AcctSID.IsAccountSid())
//Do Something
How do I retrieve the users in a given AD group?
Do I start by instantiating a PrincipalContext with a domain, username and password?
First, find the group. Then enumerate its users using GetMembers().
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var group = GroupPrincipal.FindByIdentity( context, "groupname" ))
{
var users = group.GetMembers( true ); // recursively enumerate
...
}
}
Note that there is a bug, fixed in .NET 4.0, where it will fail to enumerate more than 1500 members of the group. If you have a large group you need to use an alternative method taking advantage of the older methods in System.DirectoryServices.
Check out this article Managing Directory Security Principals in the .NET Framework 3.5 for a great overview of what you can do with System.DirectoryServices.AccountManagement in .NET 3.5.
As for retrieving the members of a group, you do this:
// build the principal context - use the NetBIOS domain name
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "DOMAIN");
// get the group you're interested in
GroupPrincipal group = GroupPrincipal.FindByIdentity("cn=YourGroupname");
// iterate over its members
foreach(Principal p in group.Members)
{
// do whatever you need to do to its members here
}
Hope this helps!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices.AccountManagement;
namespace ExportActiveDirectoryGroupsUsers
{
class Program
{
static void Main(string[] args)
{
if (args == null)
{
Console.WriteLine("args is null, useage: ExportActiveDirectoryGroupsUsers OutputPath"); // Check for null array
}
else
{
Console.Write("args length is ");
Console.WriteLine(args.Length); // Write array length
for (int i = 0; i < args.Length; i++) // Loop through array
{
string argument = args[i];
Console.Write("args index ");
Console.Write(i); // Write index
Console.Write(" is [");
Console.Write(argument); // Write string
Console.WriteLine("]");
}
try
{
using (var ServerContext = new PrincipalContext(ContextType.Domain, ServerAddress, Username, Password))
{
/// define a "query-by-example" principal - here, we search for a GroupPrincipal
GroupPrincipal qbeGroup = new GroupPrincipal(ServerContext, args[0]);
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeGroup);
// find all matches
foreach (var found in srch.FindAll())
{
GroupPrincipal foundGroup = found as GroupPrincipal;
if (foundGroup != null)
{
// iterate over members
foreach (Principal p in foundGroup.GetMembers())
{
Console.WriteLine("{0}|{1}", foundGroup.Name, p.DisplayName);
// do whatever you need to do to those members
}
}
}
}
//Console.WriteLine("end");
}
catch (Exception ex)
{
Console.WriteLine("Something wrong happened in the AD Query module: " + ex.ToString());
}
Console.ReadLine();
}
}
}
}