Monday, January 28, 2008

Change AD password with code

New cool stuff with the .Net 3.5 Framework! The System.DirectoryServices.AccountManagement namespace adds all kinds of clean methods for working with user objects. I wanted to put together a simple web part to change AD passwords - found a few good examples out there, but since I have the 3.5 Framework installed on my server I of course had to give the latest a whirl.

First, for background, take a look at this excellent article: AccountManagement Namespace

Joe & Ethan give a great overview of using the new features in 3.5.

In my web part, then, I have a three text boxes and a "Change" button - the bulk of the web part is based on the work done by Robin Meuré did here, with a few cosmetic changes but the main change done to the code in the btn_Click, posted here:

SPWeb webContext = SPControl.GetContextWeb(Context);
string strLoginName = webContext.CurrentUser.LoginName;
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain);

UserPrincipal user = UserPrincipal.FindByIdentity(domainContext, strLoginName);

try
{
string oldPasswordValue = oldpassword.Text;
string newPasswordValue = newpassword.Text;
user.ChangePassword(oldPasswordValue, newPasswordValue);
output.Text = "Password changed. Please close this browser window and log back on with your new password.";
}
catch (Exception ex)
{
output.Text += String.Format("Password couldn't be changed due to restrictions: {0}", ex.Message);

}
finally
{
user.Dispose();
domainContext.Dispose();
}


That's it! Easy-peasy.

Note that when I was getting the user object, I first tried just getting a UserPrincipal object by context:

UserPrincipal user = new UserPrincipal(domainContext);

Problem here was, when i invoked the ChangePassword method, I got the very friendly message back that: ChangePassword method can not be called on an unpersisted Principal object. So instead I get the user account info from the SPContext object, pass that in to the Directory services methods, and we're good to go.

3 comments:

Unknown said...

Hi Steve,

I have the same problem. If I try to implement a webpart to search user in AD, I get the message "principal object must be persisted before this method can be called"
How can I get the user info and pass it into directory service methods.
Here my code
"public void getObjects()
{
principalContext ctx = new PrincipalContext(ContextType.Domain, .....)
UserPrincipal up = new UserPrincipal(ctx);
up.Name = "*";
principalSearcher ps = new PrincipalSearcher();
ps.QueryFilter = up;
principalSearchResult -Principal- results = ps.FindAll();
foreach (UserPrincipal _object in results)
...."

regard Peter

Anonymous said...

Keep up the good work.

Jason Cox said...

Thanks for posting this, Steve. It helped point me in a good direction to properly grab a user from Active Directory using the UserPrincipal.

The reason the UserPrincipal constructor doesn't work is because the object created by the constructor isn't coming from Active Directory or any other store...it's intended to create a new object in the user store and thus it's only resident in memory until you persist, i.e., save it to the backing store.

Check out the following comment about this and thanks again for posting this little tidbit to change my thought direction.

"The user principal account is not persisted when it is created. To save the account, call the Save method."

From http://msdn.microsoft.com/en-us/library/bb343409.aspx