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.
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 very old project that worked like a charm for many years.
All of a sudden It seems that the following code snippet is only returning false because the "allowedAttributesEffective" property is null, and therefore thinking that the user has no rights to Modify an AD object
public static bool checkWritePermission(DirectoryEntry de) {
de.RefreshCache(new string[] { "allowedAttributesEffective" });
log.write("Write permission == " + (bool)(de.Properties["allowedAttributesEffective"].Value != null), 5);
// Outputs always -> "Write permission == False"
return de.Properties["allowedAttributesEffective"].Value != null;
}
The "original" codes taken years ago from here: How can I check if a user has write rights in Active Directory using C#?
In reality the user that executes this code should have modify permissions on all AD Objects (proven by the fact that I can Modify all objects in question via Active Directory Users and Computers Management Interface)
Any Ideas what the issue could be here?
I have also tried with ldapsearch on linux to see if I can read the property "allowedAttributesEffective" but I have not found a clear answer if that should even be the case as it is not returning anything on any LDAP object
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.
So we have created an updated version of a WSP for SharePoint 2010 due to our migration/update from 2007 to 2010.
The WSP is a event handler/reciever for ItemAdded() and we have it working as intended. Issue is that the operation seems to only work for one computer/machine and no others.
When the Item is Added to a list the WSP creates a Folder in Shared Documents library, creates a wiki page, then updates the new List Item with links to the Shared Doc and Wiki.
When triggered by Machine #1 and User #1 all operations work, when Machine #2(M2) and user #2(U2) or M3 and U3 non of the tasks take place when a new Item is created.
User #2 can log in on M1 and create a new item and all operations work. But if U1 uses M2 or M3 to create an item the events don't trigger. Machine #1 is able to trigger the event as many times as they want but no other computer is able to.
If you were able to follow is it something with the code or some sort of cache setting on the local machine or the SP server, or something else? Any help is appreciated.
Update: All machines are on the same network. Non of the machines are the server but various personal laptops. Development was done on a separate machine. All are accessing via the same URL. All users have same access. This is on our test site currently which would be switched to being production once migration/upgrade takes place.
Before current .WSP deployment we noticed the same issue but it was reverse, Machine #2 did all the updates but Machine #1 and #3 couldn't. Only thing we can think of was that those machines were the first to trigger the event after deployment.
I'm Not doing the .WSP install but our IT guy is(won't let us have access :/ but I understand) but below is the install commands he is running.
Add-SPSolution -LiteralPath "OurPath/ourFile.wsp"
Install-SPSolution -Identity ourIdentity -WebApplication http://myhost.com/ -GACDeployment
Below is the main part of the code
public class CreateWikiAndFolder : Microsoft.SharePoint.SPItemEventReceiver
{
public override void ItemAdded(SPItemEventProperties properties)
{
try
{
//this.DisableEventFiring();
base.EventFiringEnabled = false;
string sUrlOfWikiPage = string.Empty;
string sUrlOfNewFolder = string.Empty;
string sSubsiteRUL = string.Empty;
string sCurrentItemTitle = properties.ListItem["Title"].ToString();
string sWikiListName = "TR Wikis";
string sDocLibName = "Shared Documents";
string sTRListID = "TR Status";
if (sTRListID.ToUpper().Equals(properties.ListTitle.ToString().ToUpper()))
{
//Create the Folder
sUrlOfNewFolder = CreateFolder(properties.ListItem.Web, sDocLibName, sCurrentItemTitle);
//Create the Wiki
string ItemDispFormUrl = String.Concat(properties.ListItem.Web.Url, "/", properties.ListItem.ParentList.Forms[PAGETYPE.PAGE_DISPLAYFORM].Url, "?ID=", properties.ListItem.ID.ToString());
sUrlOfWikiPage = CreateWiki(properties.ListItem.Web, sWikiListName, sCurrentItemTitle, ItemDispFormUrl, sUrlOfNewFolder);
//Update the current TR Item
SPWeb myWeb = properties.ListItem.Web;
myWeb.AllowUnsafeUpdates = true;
SPListItem myListItem = properties.ListItem;
SPFieldUrlValue shareFolderURLValue = new SPFieldUrlValue();
shareFolderURLValue.Description = "Shared Folder";
shareFolderURLValue.Url = sUrlOfNewFolder ;
myListItem["SharedFolder"] = shareFolderURLValue;
myListItem.Update();
myWeb.AllowUnsafeUpdates = false;
}
base.EventFiringEnabled = true;
}
catch (Exception e)
{
//Currently throwing nothing
}
}
}
It could be a hardcoded path/url, however there is not enough information to identify the problem, I would be glad to update my answer with a more detailed theory if you provide more details or if you share some of your code.
Figured out the issue. I didn't include them with the above file code. But we were StreamWriting to a text file on the server to help us with debugging. Issue was with that, When user 1 was logged on their machine and the log files didn't exist, they would get generated. Now no other users then had read/write access to those files and so it errored out at our debug files for anyone else. But that Windows user could run it as much as they wanted as they were the owner of the file :/
I need to write a program that will take a existing local windows user, change the "Start the following program at logon field" in their environment tab and change the password to a new password.
I am using local users instead of AD, will System.DirectoryServices work? Also any tutorials on using System.DirectoryServices would be very helpful.
EDIT --
Ok, so I can find the existing user and I know how to update it. However I do not know what to put in the ???. What AD property represents "Start the following program at logon field" in their environment tab on the user settings page?
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", prams.user);
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (result == null)
{
Console.Error.WriteLine("No user of name {0} in the system", prams.user);
return -2;
}
DirectoryEntry entry = result.GetDirectoryEntry();
entry.InvokeSet("???", new object[] { newProgramLognField });
EDIT two---
I now know that the field I need is "Parameters" however I this term is very hard to search for in the MSDN because it is such a generic term. I think I have the correct one because when i run the code
DirectoryEntry entry = new DirectoryEntry("WinNT://testComputer/testUser");
Console.WriteLine("connected");
string value = (string)entry.Properties["Parameters"].Value;
Console.WriteLine(value);
I get the result
P☺CtxCfgPresent????☺CtxCfgFlags1
????☺CtxShadow????*☻☺CtxMinEncryptionLevel? #☺CtxWorkDirectory??????????????????
??????????????"C☺CtxInitialProgram??????????????????????????????????????????????
????????????????????????????????????????????????????????????????????????????????
????????
I am guessing that InitalProgram portion is close to what I want, most likely the ??? after is is the existing path that is there but has be mucked up.
Now the question is how do I write correctly to it?
Edit-3
I try changing the casting from a string to a PropertyValueCollection however that just gives me the error at run time
Unhandled Exception: System.InvalidCastException: Unable to cast object of type
'System.String' to type 'System.DirectoryServices.PropertyValueCollection'.
at UserUpdater.Program.Main(String[] args) in e:\Visual Studio 2008\Projects\
UserUpdater\UserUpdater\Program.cs:line 91
My copy of the TechNet Script Repository says that this WSH script can be used to change the password.
Set objComputer = GetObject _
("LDAP://CN=atl-dc-01,CN=Computers,DC=Reskit,DC=COM")
objComputer.SetPassword "whatever"