Search in AD from VSTO - c#

I use FW 4.5 and develop Add-in for WORD 2016.
In add-in I need search in AD, I use the next code :
string ldapPath = "LDAP://OU=Ingegneria,DC=xxx,DC=xxx";
DirectoryEntry searchRoot = GetEntry(ldapPath, adminUser, adminPassword);
DirectorySearcher search = new DirectorySearcher(searchRoot)
{
SearchScope = SearchScope.Subtree,
Filter = "(&" +
"(objectClass=user)" +
"(givenname=s*)" +
"(samaccountname=*100)" +
")"
};
search.PropertiesToLoad.Add("distinguishedname");
SearchResultCollection result = search.FindOne();
Every query take about 800 miliseconds.
BUT At the same computer , same code outside of add-in(tester) :
first search take about 800 miliseconds , and after this every search take about 25 miliseonds.
What the problem with add-in? And what can I do ?

Does it help if you cache the DirectoryEntry and DirectorySearcher?
Try this:
DirectoryEntry searchRoot;
DirectorySearcher search;
public SearchResultCollection SearchAd(string prop){
// Define other methods and classes here
string ldapPath = "LDAP://OU=Ingegneria,DC=xxx,DC=xxx";
searchRoot = searchRoot ?? GetEntry(ldapPath, adminUser, adminPassword);
search = search ?? new DirectorySearcher(searchRoot)
{
SearchScope = SearchScope.Subtree,
Filter = "(&" +
"(objectClass=user)" +
"(givenname=s*)" +
"(samaccountname=*100)" +
")"
};
search.PropertiesToLoad.Add(prop);
return search.FindOne();
}

Related

DirectoryEntry CommitChanges returns Exception has been thrown by the target of an invocation on server 2019

I tried to create reset password and user creation functions for Active Directory. On my PC with below code is works just fine without any error. But when I publish to the server, I received error: Exception has been thrown by the target of an invocation.
ADResult hasil = new ADResult();
DirectoryEntry de = new DirectoryEntry(_path, _adminID, _adminPassword, AuthenticationTypes.Secure);
DirectorySearcher ds = new DirectorySearcher(de);
string query = string.Format("(&(objectCategory=person)(sAMAccountName={0}))", user.userID);
ds.Filter = query;
ds.Sort.PropertyName = "CN";
ds.SearchScope = SearchScope.Subtree;
ds.CacheResults = false;
try
{
SearchResult sr = ds.FindOne();
if (sr == null)
{
hasil.errorCode = -1;
hasil.result = "User name not found in this domain.";
}
else
{
DirectoryEntry userCredentials = sr.GetDirectoryEntry();
userCredentials.Invoke("SetPassword", new Object[] { user.password });
userCredentials.CommitChanges();
userCredentials.Close();
hasil.errorCode = 0;
hasil.result = "Password for " + user.userID + " changed successfully.";
}
}
catch (Exception e)
{
hasil.errorCode = -1;
hasil.result = e.Message + "<br/>" + e.StackTrace + "<br/>" + e.Source;
}
return hasil;
Is there something configuration/settings that I missed on the server side?
I changed my code using
UserPrincipal
instead of
DirectoryEntry
and it works perfectly.
I use this code:
PrincipalContext PrincipalContext4 = new PrincipalContext(ContextType.Domain, "full_domain_name.com", "OU=User_OU,DC=domain_name,DC=co,DC=id", _adminID, _adminPassword);
UserPrincipal UserPrincipal1 = new UserPrincipal(PrincipalContext4, user.userID, user.password, true);
//User Logon Name
UserPrincipal1.UserPrincipalName = user.userID;
UserPrincipal1.Name = user.firstName + " " + user.lastName;
UserPrincipal1.GivenName = user.firstName;
UserPrincipal1.Surname = user.lastName;
UserPrincipal1.DisplayName = user.firstName + " " + user.lastName;
UserPrincipal1.Enabled = true;
UserPrincipal1.Save();
I still don't know why I use DirectoryEntry is not working on windows server 2019
That't not the exact error message,real error must be wrapped.You can write loggers or event logging after lines which you think could be culprit.You can check event log on that server if you can find elaborated stack Trace. You can check that user have admin privilege for that server to lookup in AD.

Retrieving members of a group that contains a subgroup

I created a dot.net form that uses c# code to retrieve members of an Active Directory group that a user selects from a drop-down, then displays the list of members (users is all I want) on the web page.
This works fine for groups that DO NOT contain sub-groups. When an Active Directory group contains users and sub-groups I receive an error.
I only want users of the group that the users selects from the drop-down. I don't want users from any sub-groups.
I have done lots of searching on the internet and testing various code suggestions.
public List<string> GetAllUsersFromGroup(string domain,
string group)
{
List<string> retVal = new List<string>();
DirectoryEntry entry = new DirectoryEntry(domain);
DirectorySearcher searcher = new DirectorySearcher("
(&(objectCategory=Group)(cn=" + group + "))");
searcher.SearchRoot = entry;
searcher.SearchScope = SearchScope.Subtree;
SearchResult result = searcher.FindOne();
var x = 0;
var txtCN = "";
var txtDispName = "";
var firstItem = 0; // Display Name
var secondItem = 1; // CN
var arraryResultsSize = result.Properties["member"].Count;
string[] deResultsArray = new string[arraryResultsSize];
foreach (string member in result.Properties["member"])
{
DirectoryEntry de = new DirectoryEntry(String.Concat(domain,
"/", member.ToString()));
if (de.Properties["objectClass"].Contains("user") &&
de.Properties["cn"].Count > 0)
{
deResultsArray[x] = de.Properties["displayName"]
[0].ToString() + "~" + de.Properties["cn"]
[0].ToString();
}
x = x + 1;
}
if (deResultsArray[0] != null) {
string[] sortSeperatedItems = sortLDAPUsers(deResultsArray);
for (var i = 0; i < sortSeperatedItems.Length/2; i++) {
txtDispName = "<tr><td>" + sortSeperatedItems[firstItem]
+ "</td>";
txtCN = "<td>" + sortSeperatedItems[secondItem] + "</td>
</tr>";
retVal.Add(txtDispName);
retVal.Add(txtCN);
firstItem = firstItem + 2;
secondItem = secondItem + 2;
}
} else {
txtDispName = "<tr><td>Group has no members</td>";
txtCN = "<td> </td></tr>";
retVal.Add(txtDispName);
retVal.Add(txtCN);
}
// retVal.Add("Array Size is: " + propCount);
return retVal;
}
public string[] sortLDAPUsers(string[] strArray) {
Array.Sort(strArray);
var newArraySize = (strArray.Length * 2);
string[] itemSeperated = new string[2];
string[] allSepItems = new string[newArraySize];
var arrayItemsString = "";
var xDName = 0;
var xCName = 1;
for (var i = 0; i < strArray.Length; i++) {
itemSeperated = strArray[i].Split('~');
allSepItems[xDName] = itemSeperated[0];
allSepItems[xCName] = itemSeperated[1];
arrayItemsString = arrayItemsString + "Length of allSepItems
is: " + allSepItems.Length + " Text is: " +
allSepItems[xDName] + " NetId is: " + allSepItems[xCName] +
"<br><br>";
xDName = xDName + 2;
xCName = xCName + 2;
}
return allSepItems;
}
When user selects an Active Directory group that contains users and sub-group(s), I receive the following error:
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 205: for (var i = 0; i < strArray.Length; i++) {
Line 206:
Line 207: itemSeperated = strArray[i].Split('~');
Line 208: allSepItems[xDName] = itemSeperated[0];
Line 209: allSepItems[xCName] = itemSeperated[1];
I think the error has to do with how I'm filtering or getting results in GetAllUsersFromGroup(). I don't know how to just get users from a group and not include any sub-group(s) in the results.
The problem with this filter (&(objectCategory=Group)(cn=<group>)) is that you are querying a specific group to iterate and grab all its members regardless of the type/objectClass, so you are left with users, groups and whatever.
Instead, you can request users that are members of this group directly using the proper filter :
new DirectorySearcher("(&(objectCategory=person)(memberOf=" + groupDN + "))");
Note that the memberOf attribute must match a DN, so given the passed in variable group (if the caller can't pass the actual group dn) you may have to grab it in the first place using the 1st filter.
The error you get then is another issue due to sorting, maybe comment this part until you get the proper results.
You do not need to work that hard when you could let Microsoft Active Directory do the work.
An LDAP search request with an LDAP filter to Resolve all members (including nested) Security Groups (requires at least Windows 2003 SP2)
(&(objectClass=user)(memberof:1.2.840.113556.1.4.1941:=CN=GroupOne,OU=Security Groups,OU=Groups,DC=YOURDOMAIN,DC=NET)
This will return all (objectClass=user) that are members of the "GroupOne".

C# LDAP against LDS

I'm trying to resolve this problem but I cannot get this to work.
The Question
What is wrong with my query?
The Code
private static void ExecuteQuery()
{
string sDomain = "10.12.14.165:389";
string sDefaultOU = "CN=GeneralUsers,CN=Company,DC=Server,DC=LDS,DC=LOCAL";
string sServiceUser = "myUser";
string sServicePassword = "myPassword";
DirectoryEntry ldapEntry = new DirectoryEntry("LDAP://" + sDomain + #"/" + sDefaultOU,
sServiceUser,
sServicePassword);
DirectorySearcher ldapSearcher = new DirectorySearcher(ldapEntry);
//Error Occurs here
SearchResultCollection ldapResult = ldapSearcher.FindAll();
}
The Error
# SearchResultCollection ldapResult = ldapSearcher.FindAll();
Exception: There is no such object on the server.
Attention
The Object DOES Exist
The problem occured in the LDS Configuration. the user I was authenticating with had no rights to the LDS, the query I was using was working fine.

Query the GC with C# and port 3268

I'm trying to query the global catalog and bind to port 3268 with C# in order to get users from the domain AND its children domains, but I get error "domain name format specified is not valid". Here's the sample code I'm using :
PrincipalContext context = new PrincipalContext(ContextType.Domain);
string path = "LDAP://" + context.ConnectedServer + ":3268/rootDSE";
DirectoryEntry searchRoot = new DirectoryEntry(path);
string configNC = searchRoot.Properties["configurationNamingContext"].Value.ToString();
DirectoryEntry configSearchRoot = new DirectoryEntry("LDAP://" + context.ConnectedServer + ":3268/" + configNC);
DirectorySearcher configSearch = new DirectorySearcher(configSearchRoot);
configSearch.Filter = ("(NETBIOSName=*)");
configSearch.PropertiesToLoad.Add("dnsroot");
configSearch.PropertiesToLoad.Add("ncname");
configSearch.PropertiesToLoad.Add("NETBIOSName");
SearchResultCollection forestPartitionList = configSearch.FindAll();
//(...)

C# populate combobox from Active Directory users

I am using this code to populate a combobox with Active Directory users but from time to time I get a COM exception at the 'for each' beside that the code works.
System.DirectoryServices.DirectoryEntry entry = new System.DirectoryServices.DirectoryEntry("LDAP://DC=DOMAIN, DC=local");
System.DirectoryServices.DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
{
try
{
//DirectoryEntry de = new DirectoryEntry(resEnt.GetDirectoryEntry());
System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
}
catch (Exception e)
{
// MessageBox.Show(e.ToString());
}
}
Is there a more efficient way to do this or resolve the error?
The first thing I'd do is include the property you want to grab from the result into the search result - that way, you don't have to do a .GetDirectoryEntry() call on each result:
using System.DirectoryServices;
DirectoryEntry entry = new DirectoryEntry("LDAP://DC=DOMAIN, DC=local");
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
// define the properties you want to be loaded into the search result object
mySearcher.PropertiesToLoad.Add("GivenName");
mySearcher.PropertiesToLoad.Add("samAccountName");
mySearcher.PropertiesToLoad.Add("sn");
foreach (SearchResult resEnt in mySearcher.FindAll())
{
try
{
string givenName = "";
string samAccountName = "";
string surName = "";
// check if you got a value - not all properties have to be filled -
// and if they're not filled, they might be "null".
if(resEnt.Properties["GivenName"] != null &&
resEnt.Properties["GivenName"].Count > 0)
{
givenName = resEnt.Properties["GivenName"].Value;
}
// samAccountName is a *must* property - it has to be set.
samAccountName = resEnt.Properties["samAccountName"].Value;
if(resEnt.Properties["sn"] != null &&
resEnt.Properties["sn"].Count > 0)
{
surName = resEnt.Properties["sn"].Value;
}
comboBox2.Items.Add(givenName + " " + surName + " " + "[" + samAccountName + "]");
}
catch (Exception e)
{
// MessageBox.Show(e.ToString());
}
}
The second point is: if you're on .NET 3.5 or newer, you can use a PrincipalSearcher and a "query-by-example" principal to do your searching; also, working with the result set using the UserPrincipal objects is a lot easier, since things like GivenName and Surname are surfaced as properties on the UserPrincipal:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for any UserPrincipal
UserPrincipal qbeUser = new UserPrincipal(ctx);
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here
UserPrincipal foundUser = found as UserPrincipal;
if(foundUser != null)
{
comboBox2.Items.Add(foundUser.GivenName + " " + foundUser.Surname + " " + "[" + foundUser.SamAccountName + "]");
}
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.
Put the foreach loop inside the catch block of the try...catch statement.

Categories