I am developing a program in C# that searched all of the users in AD and adds them to a list. Unfortunately, I have run across a case in which a particular user is not being added to the group.
Relevant code I am using to create the list (in a try/catch block):
List<User> ADUsers = new List<User>();
string domainPath = "LDAP://DC=domain,DC=local";
search.Filter = "(&(objectClass=user))";
//I was including propertiesToLoad here, but I removed it in testing
SearchResultCollection results = search.FindAll();
if (results != null)
{
foreach (SearchResult result in results)
{
if (result.Properties.Contains("samaccountname") && result.Properties.Contains("mail") && result.Properties.Contains("displayname"))
{
User objSurveyUser = new User((String)result.Properties["samaccountname"][0], (String)result.Properties["mail"][0], (String)result.Properties["displayname"][0]);
ADUsers.Add(objSurveyUser);
}
}
}
return ADUsers;
I get 900+ entries (as expected), and I have so far been able to search for every user I have tried but one.
I checked in AD to verify that the user was classified as a user, so I know it should no be getting filtered out. I also verified (in hyena) that the Pre-2k accountname (samaccountname), email address (mail), and display name were all listed and correct for this user.
I have created else blocks to catch users that are missing one or more of the above and tried to find the user that way, but to no avail.
Does anyone see anything obvious that I might be missing in my code?
Update: I changed the search filter to (objectCategory=person) and some of the users I was unable to find appeared. Getting closer.
Update 2: I think there might be an issue with the way AD Searcher works. I changed (objectCategory=person) to (|(objectCategory=person)(objectClass=user)). By the rules of logic, my returned size should grow; instead, it shrinks. |A U B| >= |A|
Update 3: I have entirely removed the line for search.filter and tried a different filter: (!(userAccountControl=2)). Both options give me the complete list of users in the base OU, but they do not search inside other OUs.
I added this code to my program for troubleshooting purposes: tbText.text = $"Found {results.Count} AD entries." That returned Found 1000 AD entries. That line held the key to the problem. I had no idea that I was getting limited in my results. A quick search on SO led me to this post.
Once I had that information, I added the appropriate code ds.PageSize = 500;, and my problem was solved.
Related
We have a .NET Framework application used for changing user passwords. It uses the System.DirectoryServices.AccountManagement assembly.
Process goes as follows:
A user fills out a simple form (username, old pw, new pw, new pw repeated).
UserPrincipal.FindByIdentity() gets called to find the user
UserPrincipal.ChangePassword() gets called to change the password
Note: Principal context type is set to DOMAIN
Note2: everything works, the issue is with the user folders.
So apparently ChangePassword() creates a user profile in C:\Users folder in the application's machine and I cannot find any information why that happens. I would understand in the context was set to Machine, but in this case it's not.
There are over 6k folders now, one for each user, it's taking up a lot of space and slowing the machine down.
I tried recreating the problem locally and apparently the ChangePassword() creates a TEMP user profile in C:\Users in my computer, but then it disappears.
Code
Configuration
container.RegisterType<PasswordChangeService>(new InjectionFactory(m => new PasswordChangeService(
new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["LdapDomain"], ConfigurationManager.AppSettings["LdapQuery"]),
new LogService(new Persistence.LogDbContext())
)));
Code for finding user, changing password
using (var user = UserPrincipal.FindByIdentity(_principalContext, IdentityType.SamAccountName, passwordChange.Username)) {
if (user == null) {
Fail(passwordChange, "User not found");
}
user.ChangePassword(passwordChange.CurrentPassword, passwordChange.NewPassword);
LogSuccess(passwordChange);
}
Can anyone tell me why the user profiles get created?
How to fix this issue? Is this a configuration or permission problem?
(optional) I've seen examples where UserPrincipal.Save() gets called after, say, ChangePassword(), but it has always worked without it, so in what situation can it be used?
Thank you.
Update
after some unsuccessful searching I found out a few things that might be worth mentioning:
1. Someone from 2009 had the same problem technet link
The last comment "Since all the code snippets utilize the ChangePasword implementation of ADSI (which causes the profile generation) the simplest way to accomplish this programmatically would be to not use ADSI but System.DirectoryServices.Protocols instead and perform an attribute modification against unicodePwd".
2. When calling ChangePassword(), user directory was sometimes not created and at at those times Event Viewer showed a couple of errors
"Windows has backed up this user profile. Windows will automatically try to use the backup profile the next time this user logs on."
"Windows cannot find the local profile and is logging you on with a temporary profile. Changes you make to this profile will be lost when you log off."
3. Every "fix" I've seen resulted in fixing the consequence, but not the cause. I still have no idea why the profiles get created, but if the technet comment is legit, I guess I need to use another implementation.
I ended up using an example from SO link and this is my result:
string ldapPath = ConfigurationManager.AppSettings["LdapPath"];
string domainWithUserName = string.Format(ConfigurationManager.AppSettings["LdapDomain"], "\\", passwordChange.Username);
DirectoryEntry directoryEntry = new DirectoryEntry(ldapPath, domainWithUserName, passwordChange.CurrentPassword);
if (directoryEntry != null) {
DirectorySearcher search = new DirectorySearcher(directoryEntry);
search.Filter = "(SAMAccountName=" + passwordChange.Username + ")";
SearchResult result = search.FindOne();
if (result != null) {
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry == null) {
Fail(passwordChange, "User not found.");
}
userEntry.Invoke("ChangePassword", new object[] { passwordChange.CurrentPassword, passwordChange.NewPassword});
userEntry.CommitChanges();
LogSuccess(passwordChange);
}
}
I still need to narrow down the AD search to a specific OU, etc.
This implementation works, no user profile gets created, Event Viewer shows nothing. It's a shame, really, because I'd rather call a few methods instead of using 15 lines of code that do the same thing.
I have a C# console application that updates Active Directory properties using data from the HR system.
Below is a sample of the code I'm using to update the manager property:-
if (ADManagerPath != adManager)
{
if (enableActiveDirectoryUpdates)
{
if (ADManagerPath.Length > 0)
{
deLDAPUpdate.Properties["manager"].Value = ADManagerPath;
managerUpdated++;
}
else
{
deLDAPUpdate.Properties["manager"].Clear();
managerBlanked++;
}
}
managerChanged = true;
}
Obviously, there are other properties that I am updating and finally if I have made any changes, there is a CommitChanges statement.
My issue is that the line will I am setting the manager, fails but only for 2 active directory accounts (which may well be for the same person).
The error codes I am receiving are (0x80005000) -2147463168. I have searched the site, and others, however most answers appear to be cases where LDAP has not been capitalized or the property/attribute you're trying to update does not exist. Both of which I do not believe apply to my situation, as I can update other accounts.
Can anybody help?
Okay I have fixed the above problem but still have a problem with these 2 accounts. The value I was reading from a database, somehow had an extra backslash in the manager path and that was the problem for setting the manager. My program now successfully can update a person's manager to 1 of these 2 accounts.
However I still believe there is a problem with the 2 accounts in question. I cannot set any property for the 2 accounts in question. Interestingly, I have developed a program, only for my use, where you enter a samAccountName and it will display and iterate through all the properties. However, this program also falls over when I attempt to use it on either of these accounts.
I write any errors to an exception table and all that error message says is "Unknown error (0x80005000)".
This program has been running for a couple of months, although it has been developed further over the past few weeks, and it is only these 2 accounts that will not update?
The code that I am using to create the various active directory connection objects is:-
string gcConnection = "GC://" + guidBindRootPath + "/<GUID=" + adID + ">";
deGUIDBind.Username = username;
deGUIDBind.Password = password;
deGUIDBind.AuthenticationType = AuthenticationTypes.Secure;
deGUIDBind.Path = gcConnection;
deLDAPUpdate.Username = username;
deLDAPUpdate.Password = password;
deLDAPUpdate.AuthenticationType = AuthenticationTypes.Secure;
string distinguishedName = deGUIDBind.Properties["distinguishedName"].Value.ToString();
string ldapConnection = "LDAP://" + getDomainNameFromDistinguishedName(distinguishedName) + "/" + distinguishedName;
deLDAPUpdate.Path = ldapConnection;
`
To reiterate, I have solved the problem of setting, the accounts in question, as the manager of other accounts, however, I can still not change any of the properties of the 2 problematic accounts. My program successfully updates other accounts.
The other program that I have written, in C#, which iterates through and displays all the properties, for a given samAccountName, also falls over for these 2 accounts.
string adDivision = convertNullToString(deLDAPUpdate.Properties["Division"].Value);
propertyDetails = propertyDetails + "Division" + "," + adDivision + Environment.NewLine;
foreach (System.DirectoryServices.PropertyValueCollection p in deLDAPUpdate.Properties)
With regard to the 2 problem accounts, this program falls over both on attempt to retrieve the division property and also when it attains to evaluate deLDAPUpdate.Properties.
I am not asking for help with the 2nd application, which is only for my own use, I only mention it since it seems to indicate that there might be a problem with the 2 accounts. This test application successfully displays the properties of other accounts.
I am trying to retrieve all of the items in a list from a SharePoint site. The fields are titled "Review Level Title", "Reviewer IDs", and "Review Level Priority". What I'm trying to do is to get the information from all three fields seperately, put them into the object I created, and then return the list with all of the objects I have created for each SharePoint item.
I have researched a lot on how to access this information from the SharePoint site, but I can not get it to work. Here is what I have created so far:
public List<OperationsReviewLevel> Get()
{
var operationsReviewLevels = new List<OperationsReviewLevel>();
ClientContext context = new ClientContext(ConfigurationManager.AppSettings["SharePointEngineeringChangeRequest"]);
var SPList = context.Web.Lists.GetByTitle("Review Levels");
CamlQuery query = new CamlQuery();
ListItemCollection entries = SPList.GetItems(query);
context.Load(entries);
context.ExecuteQuery();
foreach(ListItem currentEntry in entries)
{
operationsReviewLevels.Add(new OperationsReviewLevel(currentEntry["Review Level Title"].ToString(), currentEntry["Reviewer IDs"].ToString(), (int)currentEntry["Review Level Priority"]));
}
return operationsReviewLevels;
}
Whenever I try this code, I receive an error saying:
Microsoft.SharePoint.Client.PropertyOrFieldNotInitializedException: The property or field has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested.
I can not find any solutions to this error (in my scenario) online, and was wondering if anyone could see what I am doing wrong in this scenario.
Thanks everyone!
After reading the comment from Alessandra Amosso under my question, I ended up debugging entries. It took a lot of digging in the debugger, but I was able to find what the field names were being retrieved as. Debugging your ListItemCollection, if you go into Data, then any entry there, and then into FieldValues, you can see what each field value should be retrieved as.
In my case, all spaces were replaces with _x0020_ and the word priority was cut to just priorit due to length of the field name.
With this, I was able to change my foreach loop to:
foreach (ListItem currentEntry in entries)
{
operationsReviewLevels.Add(new OperationsReviewLevel(currentEntry["Review_x0020_Level_x0020_Title"].ToString(), currentEntry["Reviewer_x0020_IDs"].ToString(), Convert.ToInt32(currentEntry["Review_x0020_Level_x0020_Priorit"].ToString())));
}
And it now works properly.
Hope this helps anyone in the future!
Guess you're using SharePoint online, SharePoint online will remove the field special characters as staticname when creating fields, for example: Review Level Title will be ReviewLevelTitle.
Here is my test code.
foreach (ListItem currentEntry in entries)
{
Console.WriteLine(currentEntry["ReviewLevelTitle"].ToString()+'-'+ currentEntry["ReviewerIDs"].ToString()+'-'+ currentEntry["ReviewLevelPriority"]);
//operationsReviewLevels.Add(new OperationsReviewLevel(currentEntry["Review Level Title"].ToString(), currentEntry["Reviewer IDs"].ToString(), (int)currentEntry["Review Level Priority"]));
}
If you're not using SharePoint online,make sure the fields match also.
As a preface, I've looked at every StackOverflow question matched by searching for this error (25 of them or so), and none of them seemed to address the problem I'm having.
I'm building up a PermissionsDialog that inherits from System.Windows.Form. Within the method that calls dialogPermissions.ShowDialog() I am retrieving some Role objects from the database and loading them into a couple of ListBoxes. That was working just fine, but now I need to override one of the properties of the Role objects I'm adding to the listboxes using this pseudocode process:
iterate over the List of Roles
look up a matching item out of a List of Profiles using List<T>.Find()
look up a property on the Profile
build up a new Role and set the Name property as needed
add the Role to a list of Roles for the PermissionsDialog
All of that goes smoothly, but when I call dialogPermissions.ShowDialog() the underlying framework code throws an AccessViolationException.
Here is what I believe to be the relevant code:
List<UserProfile> userProfiles = new List<UserProfile>();
List<Role> allRoles = new List<Role>();
dialogData.AllRoles = new List<Role>();
using (var objectContext = this.SiteContext.CreateObjectContext())
{
userProfiles = rs.UserProfiles.FindAll().ToList();
allRoles = rs.Roles.FindAll();
}
foreach (Role role in allRoles.Where(role => role.BuiltInId == (int)BuiltInRoleId.UserProfileRole)) {
var userProfile = userProfiles.Find(up => role.Id == up.Id);
var roleToAdd = new Role {
BuiltInId = role.BuiltInId,
Description = role.Description,
DirectoryEntry = role.DirectoryEntry,
Id = role.Id,
IsBuiltInRole = role.IsBuiltInRole,
Name = null != profile ? profile.DisplayName:role.Name
};
dialogData.AllRoles.Add(roleToAdd);
}
My suspicion is that this is somehow a deferred execution issue, but triggering execution by calling ToList() on dialogData.AllRoles before calling ShowDialog() doesn't resolve the issue. When I replace profile.DisplayName with a constant string I do not get the error.
Any clues what's going on under the covers here, or how to find out what's going on, or how to approach the problem differently so I can avoid it? All suggestions welcome ;-)
CONCLUSION
So here's the actual issue, I think:
Setting the Name property of the Role to null is just fine, but when the dialog tries to create a ListBoxItem out of the Role and uses the Role.Name property for the ListBoxItem's Content property (which is an Object), that can't be set as null and throws down in the framework code that's building up the dialog. Checking to make sure I had a value in there fixes the issue.
Seems like s strange exception to throw, but there you have it....
You test for profile != null, but don't test for profile.DisplayName != null so that's the first thing that comes to mind from just looking at your sample.
Standard exception finder is to go to Debug\Exceptions and check the box for break on thrown so you can see all the state when the exception is thrown.
You can stare at this code for a week and never find the reason for the AccessViolationException. Managed code does not die from processor faults like this one.
You'll need to dig out as much info you can get from the actual exception. That requires first of all that you enable unmanaged code debugging so that you can see the stack frames in the native code. Project + Properties, Debug tab, tick the "Enabled unmanaged code debugging" option.
Next, you want to make sure that you have .pdb file for any of the native Windows DLLs. Including the ones for Active Directory, somewhat suspect in this case. Tools + Options, Debugging, Symbols and enable the Microsoft Symbol Server. Press F1 if you have an older version of Visual Studio that doesn't make it a simple checkbox click.
Reproduce the crash, the call stack should give you a good hint what native code is suspect. Post it in your question if you can't make hay of it.
i am building a csharp application and i would like a dropdown list of all users in my outlook global address book (the same one when i click on To: from outlook gui. is this possible to get this progrmaticall? what are the security requirements here?
Security ramifications, in addition to the Outlook dependency left me unable to use this approach, in the past. As a result, I ended up building this in the form of an LDAP query. Another plus is that, (in response to your other question) you will be able to extract contact information because this information is stored in the Active Directory.
DISCLAIMER: It has been almost five years since I have looked at this code, so I'm afraid I no longer fully understand the query. Hopefully it's enough to get you started, however.
DirectoryEntry adFolderObject = new DirectoryEntry();
DirectorySearcher adSearcher = new DirectorySearcher(adFolderObject);
adSearcher.SearchScope = SearchScope.Subtree;
adSearcher.Filter = "(& (mailnickname=*) (| (&(objectCategory=person)(objectClass=user)(!(homeMDB=*))(!(msExchHomeServerName=*)))(&(objectCategory=person)(objectClass=user)(|(homeMDB=*)(msExchHomeServerName=*))) ))";
foreach (SearchResult adObject in adSearcher.FindAll())
{
Console.WriteLine("CN={0}, Path={1}", adObject.Properties["CN"][0], adObject.Path);
}