The code below works just fine, however what's happening is the code limits the results to 1500 users and we have more than 1500 users. What I'm trying to do is retrieve a list of all users that are a member of a specific group. I know DirectorySearcher has a PageSize setting however, I'm unable to find a way to set DirectoryEntry PageSize will still only pulling members of that group.
Does anybody know a way to change the page size? Or maybe how to pull members of a specific group in another fashion that will accommodate pagesize?
DirectoryEntry dEntryhighlevel = new DirectoryEntry("LDAP://CN=Users,OU=MyOu,OU=Clients,OU=Home,DC=bridgeTech,DC=net");
foreach (object dn in dEntryhighlevel.Properties["member"])
{
DirectoryEntry singleEntry = new DirectoryEntry("LDAP://" + dn);
DirectorySearcher dSearcher = new DirectorySearcher(singleEntry);
//filter just user objects
dSearcher.SearchScope = SearchScope.Base;
//dSearcher.Filter = "(&(objectClass=user)(dn=" + dn + "))";
//dSearcher.PageSize = 1000;
SearchResult singleResult = null;
singleResult = dSearcher.FindOne();
if (singleResult != null)
{
string Last_Name = singleResult.Properties["sn"][0].ToString();
string First_Name = singleResult.Properties["givenname"][0].ToString();
string userName = singleResult.Properties["samAccountName"][0].ToString();
string Email_Address = singleResult.Properties["mail"][0].ToString();
OriginalList.Add(Last_Name + "|" + First_Name + "|" + userName + "|" + Email_Address);
}
singleEntry.Close();
}
This came up in another thread recently: Always getting 1500 member of distribution list using PowerShell
In short, you want to use ranged retrieval to get the membership. This is the mechanism designed to help you fetch large attributes with >1500 values in them.
While we're on this topic, I'd like to predict your next thread. :) Reading the membership of the group will yield missing results depending upon the API you use. If you are "close to the metal" and using LDAP APIs, you'll find that users in the group due to primary group membership will be missing. I'd test this with whatever approach you use after resolving the ranged retrieval issue to ensure you don't miss anyone.
More info on this here: retrieving group members/membership from active directory when members attrib doesn't work
I'm working on something similar to this at the moment and noticed that your code differs to mine slightly. I haven't had any issues with limited results using the following code structure:
DirectoryEntry dEntryhighlevel = new DirectoryEntry("LDAP://CN=Users,OU=MyOu,OU=Clients,OU=Home,DC=bridgeTech,DC=net");
DirectorySearcher dSearcher = new DirectorySearcher();
//filter just user objects
dSearcher.Filter = "(objectClass=user)";
dSearcher.PageSize = 1000;
SearchResultCollection resultCollection = dirSearcher.FindAll();
foreach (SearchResult userResults in resultCollection )
{
string Last_Name = userResults .Properties["sn"][0].ToString();
string First_Name = userResults .Properties["givenname"][0].ToString();
string userName = userResults .Properties["samAccountName"][0].ToString();
string Email_Address = userResults .Properties["mail"][0].ToString();
OriginalList.Add(Last_Name + "|" + First_Name + "|" + userName + "|" + Email_Address);
}
That should return all your users. You'll need to use LDAP search patterns in your dSearcher.Filter in order to narrow users down to a specific group - see this link for some additional help with that.
Related
I try to identify in C# ,a DB Access with thousands of queries, only the queries with fictitious parameters,
(eg. Select id, Nation, Name from someTable where Nation = [Give a Nation]).
I tried with GetOleDbSchemaTable but with no luck.
I also tried to use MSysQueries and MSysObjects from Access and then retrieve the info in C # but with no luck.
How can you help me?
You have to ref the interop - this one:
using System.Data.OleDb
Imports Microsoft.Office.Interop.Access.Dao
public void ShowParms()
{
var strPath = #"C:\Users\AlbertKallal\source\repos\DesktopFun\test44.accdb";
Database db;
DBEngine dbACE = new DBEngine();
db = dbACE.OpenDatabase(strPath);
QueryDef query;
Parameter qParm;
foreach (var query in db.QueryDefs)
{
if (Strings.Left(query.Name, 1) != "~")
{
Debug.Print("Query name = " + query.Name);
// parmaters
foreach (var qParm in query.Parameters)
Debug.Print("query parmater name = " + qParm.Name + " type = " + qParm.Type);
}
}
}
the above will list out each query - and if it has parameters - then it will list out those.
I'm trying to build a query, to list all the known computers in SCCM with a specific name.
The query looks like this:
string query = string.Format("Select Name From SMS_R_System Where Name like '" + "%" + computerName + "%" + "'");
If results are found, it puts the result(s) in a dropdown box.
My problem in these case, the output looks like this:
"instance of SMS_R_System{Name = "DC01";};"
But of course, for our use case we only need DC01 as output.
Any tips?
The full Code for the ButtonEvent:
private void ChkBtn_Click(object sender, EventArgs e)
{
string computerName = PCDropDown.Text;
lBox.Items.Clear();
SmsNamedValuesDictionary namedValues = new SmsNamedValuesDictionary();
WqlConnectionManager connection = new WqlConnectionManager(namedValues);
// Connect to remote computer.
try
{
connection.Connect(PrimarySiteServer.ToString());
// Set the query.
string query1 = string.Format("Select Name From SMS_R_System Where Name like '" + "%" + computerName + "%" + "'");
string query2 = string.Format("Select * From SMS_UserMachineRelationship WHERE ResourceName like '" + "%" + computerName + "%" + "' AND IsActive = '1' AND Types = '1'");
// Get the query results
IResultObject queryResults = connection.QueryProcessor.ExecuteQuery(query1);
// Check for results and display in infobox
bool resultsFound = false;
foreach (IResultObject queryResult in queryResults)
{
resultsFound = true;
lBox.Items.Add("Rechner ist vorhanden");
PCDropDown.Items.Add(queryResult.ToString());
}
if (resultsFound == false)
{
lBox.Items.Add("Rechnername nicht gefunden");
}
}
catch
{
MessageBox.Show("No Connection to Config-Manager - Als ZZA ausgeführt? SCCM-Servername richtig?");
}
}
Instead of adding queryResult.ToString() like you do here:
PCDropDown.Items.Add(queryResult.ToString());
you need to add the correct field of queryResult, so in this case:
PCDropDown.Items.Add(queryResult["Name"].StringValue);
Also a quick note. I don't know for who you are writing this and what the next step would be but if this is a read only application that is only used by SCCM Admins I would consider ignoring WMI and going to the SCCM DB via SQL instead. It is a lot faster, SQL has far more powerful options for queries and it does not need the integration of those strange sccm console Dlls (although that is not 100% necessary for WMI either).
If you need write access to create devices or collections etc., or you need to work with the roles the sccm access rights systems implements however WMI is the better or only choice. (And in this case I'd rather really use those strange dlls because all of the MS examples rely on them and it can be hard to translate those tutorials to the vanilla WMI solution C# offers.
I am using LDAP Directory Services in C# to search users from LDAP with some filter criteria. I want to supply multiple OR filter criteria. For example firstName, lastName, telephone etc. It works fine when I supply all filter values but gives error when I just supply one or two filter values.
Here is the sample code I am using:
var LdapSearcher = new DirectorySearcher(RootDomain,
"(&(objectclass=user)(sn=" + lastName.Trim() + ")(givenName=" + firstName.Trim() + "))");
I get the result when I supply both sn and givenName values. However, it's an OR search and user will enter either lastName or FirstName.
How to apply OR Filter in LDAP DirectorySearcher.?
You need to use the | operator. From what you've provided, your conditions are :
objectclass must be equal "user"
sn OR givenName must be equal to the provided value
Let's say the user has provided the name "John Smith". Your filter should look like :
(&(objectClass=user)(|(sn=Smith)(givenName=John)))
var LdapSearcher = new DirectorySearcher(RootDomain,
"(&(objectclass=user)" +
(!(string.IsNullOrEmpty(lastName.Trim())) ? "(sn=" + lastName.Trim() + ")" : "") +
(!(string.IsNullOrEmpty(firstName.Trim())) ? "(givenName=" + firstName.Trim() + ")" : "")
+ ")");
I want to diaplay all the names that match with the user provided name from a directory server using LDAP and bind it to grid view. Am able to achieve this task bt instead of just a name am getting other properties like LDAP://CN=Neha Shetty,OU=Users,OU=MUM,OU=Mumbai,OU=India,OU=APAC,OU=bunt,DC=xxx,DC=com. But i just want Neha Shetty. Here is my code
DirectoryEntry de = new DirectoryEntry("ADConnection");
DirectorySearcher deSearch = new DirectorySearcher(de);
//set the search filter
deSearch.SearchRoot = de;
String UserName = txt_To.Text;
// deSearch.Filter = "(&(objectCategory=user)(GivenName=*" + UserName + "*))";
deSearch = new DirectorySearcher("(&(objectCategory=user)(Name=*" + UserName + "*))");
//deSearch.SearchScope = SearchScope.Subtree;
string[] arrPropertiesToLoad = { "Surname" };
deSearch.PropertiesToLoad.AddRange(arrPropertiesToLoad);
// SearchResultCollection sResultColl = deSearch.FindAll();
SearchResultCollection sResultColl;
sResultColl = deSearch.FindAll();
Gridview1.DataSource = sResultColl;
Gridview1.DataBind();
LDAP://CN=Neha Shetty,OU=Users,OU=MUM,OU=Mumbai,OU=India,OU=APAC,OU=bunt,DC=xxx,DC=com
is the distinguished name of the entry, and is always returned in a search result that returns at least one entry. The distinguished name is used as the primary key for an entry in a directory.
Directories do not have properties, directories have attributes which are grouped according to objectClasses into entries; properties are single-valued attributes might be multi-valued. The LDAP client must specify which user attributes should be returned as one of the parameters of the search request.
Hi all I'm porting over my VBScript over to C#. And I ran into a problem that Active Directory properties retrieval is much slower in C#.
This is my incomplete C# code
foreach(string s in dictLast.Keys)
{
if(s.Contains("/"))
str = s.Insert(s.IndexOf('/'), "\\");
else
str = s;
dEntry = new DirectoryEntry("LDAP://" + str);
strUAC = dEntry.Properties["userAccountControl"].Value.ToString();
cmd.CommandText = "INSERT INTO [NOW](readTime) VALUES(\"" + test.Elapsed.Milliseconds.ToString() + "\")";
cmd.ExecuteNonQuery();
test.Reset();
test.Start();
}
If I comment out this line.
strUAC = dEntry.Properties["userAccountControl"].Value.ToString();
It runs at 11 secs. But if I don't, it runs at 2mins 35 secs. The number of records are 3700. On average each record runs at 50 secs. I'm using the Stopwatch Class.
My VBscript runs at only 39 secs (Using difference of Time). With each record either a 0 or 15 milliseconds. I'm using the difference of Timer().
Here's my VBscript
strAttributes = "displayName, pwdLastSet, whenCreated, whenChanged, userAccountControl"
For Each strUser In objList.Keys
prevTime = Timer()
strFilter = "(sAMAccountName=" & strUser & ")"
strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
adoCommand.CommandText = strQuery
Set adoRecordset = adoCommand.Execute
On Error Resume Next
If (adoRecordset.Fields("displayName") = null) Then
strCN = "-"
Else
strCN = adoRecordset.Fields("displayName")
End If
If (Err.Number <> 0) Then
MsgBox(strUser)
End If
strCr8 = DateAdd("h", 8, adoRecordset.Fields("whenCreated"))
strUAC = adoRecordset.Fields("userAccountControl")
If (strUAC AND ADS_UF_DONT_EXPIRE_PASSWD) Then
strPW = "Never expires"
Else
If (TypeName(adoRecordset.Fields("pwdLastSet").Value) = "Object") Then
Set objDate = adoRecordset.Fields("pwdLastSet").Value
dtmPwdLastSet = Integer8Date(objDate, lngBias)
Else
dtmPwdLastSet = #1/1/1601#
End If
If (dtmPwdLastSet = #1/1/1601#) Then
strPW = "Must Change at Next Logon"
Else
strPW = DateAdd("d", sngMaxPwdAge, dtmPwdLastSet)
End If
End If
retTime = Timer() - prevTime
If (objList.Item(strUser) = #1/1/1601#) Then
Wscript.Echo strCN & ";" & strUser & ";" & strPW & ";" & strCr8 & ";" & ObjChange.Item(strUser) & ";0;" & strUAC & ";" & retTime
Else
Wscript.Echo strCN & ";" & strUser & ";" & strPW & ";" & strCr8 & ";" & ObjChange.Item(strUser) & ";" & objList.Item(strUser) & ";" & strUAC & ";" & retTime
End If
Next
Any ideas what's the problem?
Please tell me if I'm not giving enough information. Thank you.
DirectorySearcher way. 1 min 8 secs.
dEntry = new DirectoryEntry("LDAP://" + strDNSDomain);
string[] strAttr = {"userAccountControl"};
foreach(string s in dictLast.Keys)
{
if(s.Contains("/"))
str = s.Insert(s.IndexOf('/'), "\\");
else
str = s;
ds = new DirectorySearcher(de, "(sAMAccountName=" + s + ")", strAttr, SearchScope.Subtree);
ds.PropertiesToLoad.Add("userAccountControl");
SearchResult rs = ds.FindOne();
strUAC = rs.Properties["userAccountControl"][0].ToString();
cmd.CommandText = "INSERT INTO [NOW](readTime) VALUES(\"" + test.Elapsed.Milliseconds.ToString() + "\")";
cmd.ExecuteNonQuery();
test.Reset();
test.Start();
}
where strDNSDomain is the defaultNamingContext. I've tried with domain name but it runs worse, at 3 mins 30 secs.
Looking at someone else's code. Would there be a difference if we omit the domain part?
using (var LDAPConnection = new DirectoryEntry("LDAP://domain/dc=domain,dc=com", "username", "password"))
And just use "LDAP://dc=domain,dc=com" instead.
Work around. Instead of binding each user and getting the properties. I stored all the properties in the first search for lastLogon instead. And output using StreamWriter.
Both DirectoryEntry and ADOConnection use ADSI underlying. There shouldn't be any performance difference.
The only reason for the performance difference is that you are trying to retrieve two different sets of data.
In your VBScript, you are setting ""displayName, pwdLastSet, whenCreated, whenChanged, userAccountControl" to strAttributes. ADSI is going to load these five attributes back from AD only.
In your C# code, you didn't call the RefreshCache method to specify what attributes that you like to load. So, when you access DirectoryEntry.Properties, it automatically calls a RefreshCache() for you without passing in any attributes for you. By default, ADSI will return all non-constructed attributes to you (pretty much all attributes) if you don't specify what attributes to load.
Another problem is that in your VBscript, you run only one LDAP query while in your C# code, you are running many LDAP queries. Each of the DirectoryEntry.RefreshCache() is going to translate to one single LDAP query. So, if you are trying to access 1000 objects, you are going to run 1000 different LDAP queries.
Take a relational database analogy, in VBscript, you are running
SELECT * FROM USER_TABLE
In C# code, you are running multiple times of the following queries
SELECT * FROM USER_TABLE WHERE id = #id
Of course, the C# code will be slower.
To do similar thing in C# code, you should use DirectorySearcher instead of DirectoryEntry.
Similarly, you need to remember to specify DirectorySearcher.PropertiesToLoad in order to specify what attributes to return from a LDAP query. If you don't specify, it will return all non-constructed attributes to you again.
Here are couple of things you can do
Enable Audit log in the LDAP server and see how your requests are going through. Audit logs will show you how much time it is taking for each request from your application and how many connections are opened etc.
Use System.DirectoryServices.Protocols which can make Asynchronous calls to LDAP. Check this sample post. Another advantage of using this name space is that you can specify attributes.
Close connection properly.
use DirectorySearcher.PropertiesToLoad to load only required properties instead of all properties.
what i see from vbscript following is a bit closer .. copying from some project, kindly test
DirectoryEntry de = new DirectoryEntry("ldap://domainname");
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(&(ObjectCategory=user)(sAMAccountName="+strUser+"))";
deSearch.PropertiesToLoad.Add("displayName");
deSearch.PropertiesToLoad.Add("pwdLastSet");
deSearch.PropertiesToLoad.Add("whenCreated");
deSearch.PropertiesToLoad.Add("whenChanged");
deSearch.PropertiesToLoad.Add("userAccountControl);
deSearch.SearchScope = SearchScope.Subtree;
SearchResult sr = deSearch.FindOne();
This is the correct way to read the property:
If searchResult.Properties.Contains(PropertyName) Then
Return searchResult.Properties(PropertyName)(0).ToString()
Else
Return String.Empty
End If