I'm looking for a way to create Active Directory users and set their password, preferably without giving my application/service Domain Admin privileges.
I've tried the following:
DirectoryEntry newUser = _directoryEntry.Children.Add("CN=" + fullname, USER);
newUser.Properties["samAccountName"].Value = username;
newUser.Properties["userPassword"].Value = password;
newUser.Properties["mail"].Value = email;
newUser.CommitChanges();
The user is created, but it seems the password is never set on the user.
Does anyone have an idea on how to set the user's password initially when creating the user? I know about
.Invoke("SetPassword", new object[] { password })
But that requires my code to be run with Domain Admin privileges. As I don't really see the point to grant my code Domain Admin privileges, just to set the initial password (I also allow user password resets, but those run in the context of that particular user), I am hoping someone has a clever solution that doesn't require me to do so.
Thanks in advance!
You can do this whole process much easier now with System.DirectoryServices.AccountManagement (long as you're on .Net 3.5):
See here for a full rundown
Here's a quick example of your specific case:
using(var pc = new PrincipalContext(ContextType.Domain))
{
using(var up = new UserPrincipal(pc))
{
up.SamAccountName = username;
up.EmailAddress = email;
up.SetPassword(password);
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
I'd use #Nick's code (wrapped in using statements so the context and principal are disposed properly). As for privileges, you'll need to at least have enough privileges on the OU where you are creating the user to create and manage objects. I'd create a specific user under which your program will run and give it just enough privileges to do the tasks that it needs in that specific OU and no more.
Yes can also use below code to create bulk of users
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 0; i < 10; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}
The actual attribute for the password is unicodePwd, which requires a specific format that is described in the documentation. But this is how you can do it:
newUser.Properties["unicodePwd"].Value = Encoding.Unicode.GetBytes("\"NewPassword\"");
Doing it this way, you can create the user with a password in one step.
Related
I need to reset windows password of any user through my .Net application. I am using the user's username to get its Directory Entry from AD server. I got these two different methods for changing password :
entry.Invoke("ChangePassword", oldPass, newPass);
&
entry.Invoke("SetPassword", "pass#123");
But I am getting the following error when am trying these methods on live AD server :
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
I have 2 AD servers. One of them is live and another is for testing purpose. I just want to check if my code is working or not. Since, access is denied on live server I can not change and check later my own password through code.
And if I am using the test AD server to change password, I don't know how to check whether the pasword is changed or not.
Kindly give any suggestions to check if my code is working properly or not.
Thanks in advance.
I think you're not getting a proper context setup before you call the invoke. Here's what I use for something similar. You'll need to set your own variables:
I'm using System.DirectoryServices.AccountManagement to get the functions.
//Domain related info
string _DCToUse = "myserver.domain.local";
string _ADConDomain = "DC=domain,DC=local";
string _AdDomain = "domain";
string _ADAdminUser = "administrator";
string _ADAdminPass = "password";
//User specific
string _UserName = "jsmith";
string _CurrentPass = "oldPass";
string _NewPass = "newPass";
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, _DCToUse,
_ADConDomain, _ADDomain+#"\"+_ADAdminUser, _ADAdminPass);
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, _UserName);
if (user == null)
{
string ADErrorMsg = "Couldn't find user, check your spelling.";
return Changed;
}
user.ChangePassword(oldPass, newPass);
user.Save();
I built a test Active Directory server in Window 2008 and I also run the DNS server on it. On my client machine which runs the C# application, I can authenticate the user against the Active directory server using the function below:
public static UserPrincipal GetUserPrincipal(string usrName,string pswd,string domainName)
{
UserPrincipal usr;
PrincipalContext ad;
// Enter Active Directory settings
ad = new PrincipalContext(ContextType.Domain, domainName,usrName,pswd);
//search user
usr = new UserPrincipal(ad);
usr.SamAccountName = usrName;
PrincipalSearcher search = new PrincipalSearcher(usr);
usr = (UserPrincipal)search.FindOne();
search.Dispose();
return usr;
}
In a separate logic I tried to retrieve a user back from the server using a user name. I used the functions below:
public static DirectoryEntry CreateDirectoryEntry()
{
// create AD connection
DirectoryEntry de = new DirectoryEntry("LDAP://CN=Users,DC=rootforest,DC=com","LDAP","password");
de.AuthenticationType = AuthenticationTypes.Secure;
return de;
}
public static ResultPropertyCollection GetUserProperty(string domainName, string usrName)
{
DirectoryEntry de = CreateDirectoryEntry();
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(SamAccountName=" + usrName + ")";
SearchResult results = deSearch.FindOne();
return null;
}
However, I got no response back from the LDAP server at all, not even an exception. Am I missing certain settings on LDAP server, any of you able to see a flaw in my code (pls don't mind the hard code values, I was testing with this code).
As part of my troubleshooting, I confirmed that I can ping to the rootforest.com from the client machine. I confirmed the user with property samaccountname "LDAP" exists. My path seems to be right because when I go onto the LDAP server and type :
dsquery user -name LDAP*
I got the following:
CN=LDAP L. LDAP,CN=Users,DC=rootforest,DC=com
Any help would be greatly appreciated, I've spent most of my day troubleshooting and researching this little bugger and I think it could be something small which I overlooked.
I don't understand why you're using the new PrincipalContext / UserPrincipal stuff in your first example, but fall back to the hard to use DirectoryEntry stuff in your second example.... doesn't really make sense... also: your second function GetUserProperty seems to return null always - typo or not??
Since you're on already using the System.DirectoryServices.AccountManagement (S.DS.AM) namespace - use it for your second task, too! Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
public static ????? GetUserProperty(string domainName, string usrName)
{
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, usrName);
if(user != null)
{
// return what you need to return from your user principal here
}
else
{
return null;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
I think your code have a few problems:
Why do you return null in your GetUserProperty() function? You should return results instead.
The attribute you are using in your search filter is misspelled. Use sSAMAccountName instead. Furthermore extend your query to search only for user accounts. Here is an example: (&(objectCategory=person)(objectClass=user)(sAMAccountName=usrName))
You could also use the UserPrincipal class to search for an identity in Active Directory. The UserPrincipal class provides a static method called FindByIdentity() to search for a user identity.
Hope, this helps.
public Object IsAuthenticated()
{
String domainAndUsername = strDomain + "\\" + strUser;
***DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, strPass);***
SearchResult result;
try
{
//Bind to the native AdsObject to force authentication.
DirectorySearcher search = new DirectorySearcher(entry) { Filter = ("(SAMAccountName=" + strUser + ")") };
search.PropertiesToLoad.Add("givenName"); // First Name
search.PropertiesToLoad.Add("sn"); // Last Name
search.PropertiesToLoad.Add("cn"); // Last Name
result = search.FindOne();
if (null == result)
{
return null;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
return new Exception("Error authenticating user. " + ex.Message);
}
return user;
}
In the above code segment, is there a way to retrieve the user's Windows login password so that the LDAP authentication works without asking the user his password another time?
Can the value for "strPass",that is being passed when DirectoryEntry object is being created, be retrieved by any way?
The password does not exist anywhere. It would be a big security hole if it did.
Also, BTW, get rid of the try/catch block. It's doing nothing but hiding the reason for the exception.
Use the ActiveDirectoryMemebershipProvider - You can authenticate without writing code, thus eliminating the login scenario you currently have.
You could set up Windows Authentication in your ASP.NET app.
http://msdn.microsoft.com/en-us/library/ff647405.aspx
Once you set this up, only authenticated users have access to the protected parts of your site.
This gives you access some key bits of information.
For example:
System.Web.HttpContext.Current.User.Identity.Name - gives the name (domain\username) of an authenticated user.
System.Web.HttpContext.Current.User.IsInRole("role_name_here") - will tell you if the authenticated user is in a given role.
Authentication can be tricky to do for the first time - please do not ask a user for their windows password - this is a security risk - allow IIS and the .NET framework take care of this for you. The article above may be a bit long and seem a bit complicated but it has a lot of good information in it.
I am using a WCF service to expose certain Active Directory management functions to our help desk staff without giving them the group membership required to manipulate AD directly. Adding users to and removing users from groups is working like a champ with existing users, but every time I create a new user it throws back this fun code:
The server is unwilling to process the request. (Exception from HRESULT: 0x80072035)
The code I use to add the user to the group is
public bool AddGroupToUser(string userDn, string groupDn)
{
try
{
DirectoryEntry groupEntry = LdapTools.GetDirectoryEntry(groupDn);
groupEntry.Properties["member"].Add(userDn);
groupEntry.CommitChanges();
groupEntry.Close();
return true;
}
catch (DirectoryServicesCOMException)
{
return false;
}
}
Everything I've read on the subject is rather vague and I can't seem to find out WHY the exception is being triggered. Any ideas?
UPDATE
This is the code I use to create the user in AD:
try
{
DirectoryEntry container = GetDirectoryEntry(storageOu);
DirectoryEntry newUser = container.Children.Add("CN=" + employee.FullName, "user");
newUser.Properties["sAMAccountName"].Value = employee.Username;
newUser.Properties["displayName"].Value = employee.FullName;
newUser.Properties["givenName"].Value = employee.FirstName;
newUser.Properties["sn"].Value = employee.LastName;
newUser.Properties["department"].Value = departmentName;
newUser.Properties["userPrincipalName"].Value = employee.Username + "#APEX.Local";
newUser.CommitChanges();
newUser.Invoke("SetPassword", new object[] { employee.Password });
newUser.CommitChanges();
AdsUserFlags userSettings = AdsUserFlags.NormalAccount;
newUser.Properties["userAccountControl"].Value = userSettings;
newUser.CommitChanges();
ldapPath = newUser.Path;
newUser.Close();
container.Close();
}
catch (DirectoryServicesCOMException e)
{
// Something went wrong... what???
}
catch (Exception e)
{
// Something else went wrong
}
The new user can login, and can be manipulated using the standard MS tools.
Apparently, unless I'm missing an important step here, the issue is time. When forcing the system to sleep for 8 seconds before attempting to add groups to the new user the process works. If I do it any sooner than the 8 second mark it fails.
I'm marking this answer as correct unless anybody can provide me a better solution.
Try:
public bool AddUserToGroup(string userName, string groupName)
{
bool done = false;
GroupPrincipal group = GroupPrincipal.FindByIdentity(context, groupName);
if (group == null)
{
group = new GroupPrincipal(context, groupName);
}
UserPrincipal user = UserPrincipal.FindByIdentity(context, userName);
if (user != null & group != null)
{
group.Members.Add(user);
group.Save();
done = (user.IsMemberOf(group));
}
return done;
}
Reference:
http://www.c-sharpcorner.com/UploadFile/dhananjaycoder/activedirectoryoperations11132009113015AM/activedirectoryoperations.aspx
As it mentioned in here, can you tell us that you set the password of the newly created users? In the reference, it says that you should SetPassword of the user before doing anything with it.
Time issue could occur from a Active Directory replication issue.
Once, I gave a product to my customer which creates user over Active Directory, which has over 10.000 records in it, with the given informations on a SharePoint form and then program adds the user to a SharePoint group. The problem was, SharePoint throws an error about the newly created user, it says user does not exist in AD.
So, one of out system engineer told us about the replication opearation on the Active Directory that it could be the source of the problem and it was true.
For the solution, program tries to do the job for 10 times with 1 second sleeps. No problem occurred so far. So as a solution, I would suggest you to check out the AD for the existence of replication method.
P.S: When I asked questions to my customers system engineers about the replication operation, they all rejected the existence of AD replication operation and told me that the program has the problem. They believed me when we created a new user in AD from a computer, we couldn't see the user for 5 seconds on an another computer.
I posted a question re LDAP account management, but after exploring this, it's not what i'm after. I've managed to find two ways of creating users on a machine, and i find one is much neater than the other, however, i am uncertain how to convert the first option over to the second option entirely.
This was my first solution:
Process MyProc = new Process();
MyProc.StartInfo.WorkingDirectory = System.Environment.SystemDirectory;
MyProc.StartInfo.FileName = "net.exe";
MyProc.StartInfo.UseShellExecute = false;
MyProc.StartInfo.RedirectStandardError = true;
MyProc.StartInfo.RedirectStandardInput = true;
MyProc.StartInfo.RedirectStandardOutput = true;
MyProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
MyProc.StartInfo.Arguments = string.Format(#" user {0} {1} /ADD /ACTIVE:YES /EXPIRES:NEVER /FULLNAME:{0}"" /PASSWORDCHG:NO /PASSWORDREQ:YES", username, password);
MyProc.Start();
MyProc.WaitForExit();
int exit = MyProc.ExitCode;
MyProc.Close();
return exit == 0;
And this was my second (preffered) solution:
DirectoryEntry root = GetDELocalRoot();
DirectoryEntry user = root.Children.Add(username, "user");
//TODO: Always Active
//TODO: Never Expires
//TODO: No Password Change
//TODO: Password Required
user.Properties["description"].Value = "Account for running the MicaService and handling updates.";
user.Invoke("SetPassword", new object[] { password });
user.CommitChanges();
user.Close();
I would like to map all the settings in my TODO: from the first solution into my second neater solution.
I have tried the following line as well:
user.Properties["userAccountControl"].Value = ADS_USER_FLAG.ADS_UF_NORMAL_ACCOUNT | ADS_USER_FLAG.ADS_UF_PASSWD_CANT_CHANGE | ADS_USER_FLAG.ADS_UF_DONT_EXPIRE_PASSWD;
But this does not work as the property does not exist in cache.
NOTE: the GetDELocalRoot() = return new DirectoryEntry("WinNT://" + Environment.MachineName);
Thanks for any input!
Regards
Tris
Check out my friend Richard Mueller's web site which has lots of useful information and reference material on what those two providers - WinNT for local machine accounts vs. LDAP for network accounts - have to offer.
There's also a Excel sheeet with all attributes that the WinNT provider exposes - it's a lot less than what the LDAP provider has, so I'm not sure if you'll be able to set all the properties you're looking for.
Marc