I'd like to implement a function that searches an ldap server (name, phone number etc.)
Here's what I wrote (the server address is a phony one, but the real one has the same pattern)
DirectoryEntry de = new DirectoryEntry("LDAP://aet7ldap.phony.com")
DirectorySearcher ds = new DirectorySearcher(de);
var test = ds.FindAll();
I know that there are other constructors (string path, string user, string password), but I don't know my username or password and I'm not sure if I need one. So please help me figure out how to do it without these parameters (if that is possible).
I've tried to write a filter as well but that's another thing, because first I need to get my connection right. But can I assume that I have to use these parameters (or column names?) I keep reading everywhere (such as 'gn' for given name and so on)?
sounds like you're trying to connect to Active Directory using the Directory entry method to find a user, you haven't mentioned if you're doing anything to the user you're looking for once they're found, so I'll just give you code that finds the user for you.
using(DirectoryEntry de = new DirectoryEntry("LDAP://servername/DC=phony,DC=com"))
using(DirectorySearcher ds = new DirectorySearcher(de))
{
ds.Filter="(&(objectClass=user)(sAMAccountName="username"))";
//I don't know exactly what criteria you're using to find the user
ds.Filter="(&(objectClass=user)(distinguishedname="")(givenname=""))"
ds.SearchScope = SearchScope.Subtree;
//performing the search and assigning the result to result
SearchResult result = ds.FindOne();
if (result != null)
{
using(DirectoryEntry user = result.GetDirectoryEntry())
{
//put code here to deal with the user as you see fit.
}
lblOutput.Text = "User " + userName + " was found.";
}
}
The Filter is the most important part to find the user you're looking for, the & means and so in the first example I gave in the code above, you're looking for an object with the class user AND the username username. Its easy enough to figure out. Here is a link to a list of all Active Directory attributes, you should be able to find what you're looking for there.
http://www.selfadsi.org/user-attributes.htm
Regards,
Tory Hill
Related
When I try to update the Name field (corresponds to the CN) on UserPrincipal (Principal, really), I get an error "The server is unwilling to process the request" on the call to UserPrincipal.Save().
I've checked to make sure there isn't another object in the same OU with the same Name (CN).
The PrincipalContext I'm operating at is the domain root (not exactly at the OU level where the user account exists).
What reason might there be for this error? Is it something that might be security policy related (even though I'm able to update all the other fields)?
using (var context = new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["domain"], ConfigurationManager.AppSettings["rootDN"], ContextOptions.Negotiate, ConfigurationManager.AppSettings["username"], ConfigurationManager.AppSettings["password"])) {
var user = UserPrincipal.FindByIdentity(context, IdentityType.Sid, "..."); // SID abbreviated
user.Name = "Name, Test";
user.Save();
}
The user I am using to create the PrincipalContext has the security rights to modify AD objects. If I update any other of the other fields (e.g. Surname, GivenName), everything works fine.
EDIT:
I've been able to accomplish what I need to do (using ADSI), but I have to run the following code under impersonation. The impersonation code is ugly, and the code below breaks away from the other way I'm updating AD data (using DirectoryServices.AccountManagement), so I'd like to get a better solution.
using (var companyOU = new DirectoryEntry("LDAP://" + company.UserAccountOU)) {
companyOU.Invoke("MoveHere", "LDAP://" + user.DistinguishedName, "cn=Name\, Test");
}
This is a cleaner way
using (var context = new PrincipalContext(ContextType.Domain))
{
var group = GroupPrincipal.FindByIdentity(context, groupName);
group.SamAccountName = newGroupName;
group.DisplayName = newGroupName;
group.Save();
var dirEntry = (DirectoryEntry)group.GetUnderlyingObject();
dirEntry.Rename("CN=" + newGroupName);
dirEntry.CommitChanges();
}
The only way I've found to do this is in the EDIT section in my question. Basically, you cannot use the UserPrincipal class. There is something special about the CN attribute, and you need to drop down a level and use DirectoryEntry, an LDAP string, and invoke the "MoveHere" ADSI command to rename the user account.
Suppose you have connected to Active Directory using the simple syntax:
string adPath = "LDAP://server.domain.com/CN=John,CN=Users,dc=domain,dc=com";
DirectoryEntry userEntry = Settings.GetADEntry(adPath);
Now, you find that you would like to see an attribute for that user.
Let's try to display the mail attribute (which stands for email address):
Console.WriteLine("User's mail attribute is " + userEntry.Properties["mail"]);
How can I delete the mail attribute value, since setting it to an empty string will not throw an error?
It turns out to be pretty simple, albeit not very commonly used...
string adPath = "LDAP://server.domain.com/CN=John,CN=Users,dc=domain,dc=com";
DirectoryEntry userEntry = Settings.GetADEntry(adPath);
userentry.Properties["mail"].Clear();
userentry.CommitChanges();
Not sure that you can delete it since user objects usually follow a company schema but maybe something like the following will work:
userEntry.Properties["mail"] = null;
or maybe:
userEntry.Invoke("Put", "mail", null);
then:
userEntry.CommitChanges();
Im wondering how to get a list of all computers / machines / pc from active directory?
(Trying to make this page a search engine bait, will reply myself. If someone has a better reply il accept that )
If you have a very big domain, or your domain has limits configured on how how many items can be returned per search, you might have to use paging.
using System.DirectoryServices; //add to references
public static List<string> GetComputers()
{
List<string> ComputerNames = new List<string>();
DirectoryEntry entry = new DirectoryEntry("LDAP://YourActiveDirectoryDomain.no");
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = ("(objectClass=computer)");
mySearcher.SizeLimit = int.MaxValue;
mySearcher.PageSize = int.MaxValue;
foreach(SearchResult resEnt in mySearcher.FindAll())
{
//"CN=SGSVG007DC"
string ComputerName = resEnt.GetDirectoryEntry().Name;
if (ComputerName.StartsWith("CN="))
ComputerName = ComputerName.Remove(0,"CN=".Length);
ComputerNames.Add(ComputerName);
}
mySearcher.Dispose();
entry.Dispose();
return ComputerNames;
}
What EKS suggested is correct, but is performing a little bit slow.
The reason for that is the call to GetDirectoryEntry() on each result. This creates a DirectoryEntry object, which is only needed if you need to modify the active directory (AD) object. It's OK if your query would return a single object, but when listing all object in AD, this greatly degrades performance.
If you only need to query AD, its better to just use the Properties collection of the result object. This will improve performance of the code several times.
This is explained in documentation for SearchResult class:
Instances of the SearchResult class are very similar to instances of
DirectoryEntry class. The crucial difference is that the
DirectoryEntry class retrieves its information from the Active
Directory Domain Services hierarchy each time a new object is
accessed, whereas the data for SearchResult is already available in
the SearchResultCollection, where it gets returned from a query that
is performed with the DirectorySearcher class.
Here is an example on how to use the Properties collection:
public static List<string> GetComputers()
{
List<string> computerNames = new List<string>();
using (DirectoryEntry entry = new DirectoryEntry("LDAP://YourActiveDirectoryDomain.no")) {
using (DirectorySearcher mySearcher = new DirectorySearcher(entry)) {
mySearcher.Filter = ("(objectClass=computer)");
// No size limit, reads all objects
mySearcher.SizeLimit = 0;
// Read data in pages of 250 objects. Make sure this value is below the limit configured in your AD domain (if there is a limit)
mySearcher.PageSize = 250;
// Let searcher know which properties are going to be used, and only load those
mySearcher.PropertiesToLoad.Add("name");
foreach(SearchResult resEnt in mySearcher.FindAll())
{
// Note: Properties can contain multiple values.
if (resEnt.Properties["name"].Count > 0)
{
string computerName = (string)resEnt.Properties["name"][0];
computerNames.Add(computerName);
}
}
}
}
return computerNames;
}
Documentation for SearchResult.Properties
Note that properties can have multiple values, that is why we use Properties["name"].Count to check the number of values.
To improve things even further, use the PropertiesToLoad collection to let the searcher know what properties you are going to use in advance. This allows the searcher to only read the data that is actually going to be used.
Note that the DirectoryEntry and DirectorySearcher objects should
be properly disposed in order to release all resources used. Its best
done with a using clause.
An LDAP query like: (objectCategory=computer) should do the trick.
if you only want to get the enabled computers:
(&(objectclass=computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
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
I have read the following properties from AD,
TerminalServicesProfilePath
TerminalServicesHomeDirectory
TerminalServicesHomeDrive
I've tried DirectoryEntry and DirectorySearcher. But they does not include the properties.
I found some example in vbscript and VC to read them.
However I failed to make it working in C#. Am I missing some tricky thing?
EDIT: Am I have to run it on "Windows Server" to make it works? Can it be read from win XP?
I think you can use the InvokeGet method on your DirectoryEntry, passing in the name of the property you want to read.
2008-12-10 11:50 CET — Edited in response to the comment
If I specify a garbage property name, I get the same COM exception. Are you sure the properties you're trying to retrieve are part of the AD schema?
Just to make sure, the code I'm using is as follows:
using (DirectorySearcher searcher = new DirectorySearcher("(cn=Test)"))
{
SearchResult result = searcher.FindOne();
if (result != null)
{
DirectoryEntry entry = result.GetDirectoryEntry();
string s = entry.InvokeGet("TerminalServicesHomeDrive") as string;
MessageBox.Show(s ?? "null");
}
}
I don't remember exactly, but it's something like this:
//user is a DirectoryEntry
IADsTSUserEx adsiUser = (IADsTSUserEx)user.NativeObject;
then you can get the TerminalServices properties you want via adsiUser.
From my experience you're better off developing on a Windows Server with access to AD due to the libraries you use. Then you'll probably make the above work, too :)
This works for me:
DirectoryEntry user = new DirectoryEntry("LDAP://" + sLDAP_SERVER + "/cn=" + SAMAccount + "," + sLdapFullPath, sUser, sPwd);
//ActiveDs.IADsUser iADsUser = (ActiveDs.IADsUser)user.NativeObject;
ActiveDs.IADsUser cont = null;
cont = user.NativeObject as ActiveDs.IADsUser;
TSUSEREXLib.IADsTSUserEx m_TsUser = (TSUSEREXLib.IADsTSUserEx)cont;
int m_TSLogonDisabled = 0;
m_TsUser.AllowLogon = m_TSLogonDisabled;