Friday, October 05, 2007

AD Change Password WebPart

Update 30/1/2009

See the latest post about this at zevenseas AD ChangePassword WebPart released where you can find the link download this ;)

 

It was always a nice to have for our extranet environment to have a webpart available within SharePoint to let users change their password instead of using an isolated .NET web application (no.. not a SharePoint WebApplication ;)). As it turned out in the last couple of days it became a must-have since the webapplication didn't seem to work properly anymore. Please note that everything did run smoothly for 2yrs. Although a couple of weeks ago we migrated the environment from 2003 to 2007, ever since users received errors like "The server is not operational" when they tried to change their password.

So I once again turned on my best friend Google and try to find any free/opensource ChangePassword webparts. Unfortunately I couldn't find one so I wrote one myself (more fun anyway eh? ;)) Luckily enough I could re-use some of the code that I used to build the webapplication. 

So how does my webpart look like in code?

using ActiveDs;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System;
using System.DirectoryServices;
using System.Web.UI.WebControls;


namespace ChangePassword
{
    public class ChangePasswordWebpart : System.Web.UI.WebControls.WebParts.WebPart
    {

        private TextBox oldpassword;
        private TextBox newpassword;
        private TextBox checknewpassword;

        private LinkButton btn;
        private Label output;


        protected override void CreateChildControls()
        {
            this.oldpassword = new TextBox();
            this.oldpassword.TextMode = TextBoxMode.Password;
            this.Controls.Add(oldpassword);

            this.newpassword = new TextBox();
            this.newpassword.TextMode = TextBoxMode.Password;
            this.Controls.Add(newpassword);

            this.checknewpassword = new TextBox();
            this.checknewpassword.TextMode = TextBoxMode.Password;
            this.Controls.Add(checknewpassword);

            this.btn = new LinkButton();
            this.btn.Click += new EventHandler(btn_Click);
            this.btn.Text = "Change Password";
            this.Controls.Add(btn);

            this.output = new Label();
            this.Controls.Add(output);

            base.CreateChildControls();
        }

        
        void btn_Click(object sender, EventArgs e)
        {

            if (newpassword.Text.ToString() == checknewpassword.Text.ToString())
            {

                SPWeb webContext = SPControl.GetContextWeb(Context);
                string strLoginName = webContext.CurrentUser.LoginName;

                int iPosition = strLoginName.IndexOf("\\") + 1;
                strLoginName = strLoginName.Substring(iPosition);

                

                DirectoryEntry entry = new DirectoryEntry("LDAP://domain.com", strLoginName, oldpassword.Text.ToString(), AuthenticationTypes.Secure);
                DirectorySearcher search = new DirectorySearcher(entry);
                search.Filter = "(SAMAccountName=" + strLoginName + ")";
                search.SearchScope = SearchScope.Subtree;
                search.CacheResults = false;

                SearchResultCollection results = search.FindAll();
                if (results.Count > 0)
                {
                    foreach (SearchResult result in results)
                    {
                        try
                        {
                            entry = result.GetDirectoryEntry();
                        }
                        catch (Exception error) { output.Text += "<BR>" + error.Message.ToString(); }

                    }

                    try
                    {
                        entry.Invoke("ChangePassword", new object[] { oldpassword.Text.ToString(), newpassword.Text.ToString() });
                        entry.CommitChanges();
                        output.Text += "<BR> Password is changed";
                    }
                    catch (Exception)
                    {
                        output.Text += "<b> Password couldn't be changed due to restrictions<b>";
                    }
                }
                else
                {
                    output.Text += "<BR> User not found or bad password";
                }
            }

            else
            {
                output.Text += "<BR>Passwords don't match";
            }

        }

        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {

            string strLoginName = string.Empty;

            try
            {
                SPWeb webContext = SPControl.GetContextWeb(Context);
                strLoginName = webContext.CurrentUser.LoginName;
            }
            catch (Exception) 
            {
                output.Text += "<BR> Please sign in first using the 'Sign In' button above";
            }

            if (strLoginName != string.Empty)
            {
                writer.Write("<table border=0>");
                writer.Write("<tr>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("Current password:");
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                oldpassword.RenderControl(writer);
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("</td>");
                writer.Write("</tr>");
                writer.Write("<tr valign='top'>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("New password:");
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                newpassword.RenderControl(writer);
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("</td>");
                writer.Write("</tr>");
                writer.Write("<tr valign='top'>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("Confirm new password:");
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                checknewpassword.RenderControl(writer);
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("</tr>");
                writer.Write("<tr valign='top'>");
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                btn.RenderControl(writer);
                writer.Write("</td>");
                writer.Write("<td class='ms-vb'>");
                writer.Write("</td>");
                writer.Write("</tr>");
                writer.Write("</table>");
                output.RenderControl(writer);
            }
            else { output.RenderControl(writer); }

           
        }
    }


}

You have to reference the "System.DirectoryServices" and the COM "Active DS Type Library", also when you package everything together, make sure that the Interop.ActiveDs.dll is included otherwise it doesn't work.

And at runtime it looks like this:

 

Hope this helps someone who is looking for the same as I was ;)

58 comments:

  1. Robin

    Your webpart is very very helpful. But I have a situation very similar and I am looking for a solution, hopefuly you can help me with that.
    I have some AD users which are using their account just to check their emails through our OWA. After 90 days their password will expire automatically (Company's password policy). How can I have them change their password after 90 days. What happens is that after about 90 days they forget that their password is about expiring and after it expires they call me and ask me to reset the password.
    Please help me in this if you can.

    Thanks

    ReplyDelete
  2. Robin,
    I had a similar need for a change password capability, but I decided to implement it as a MOSS Application page (an aspx page in _layouts, as described in one of Ted Pattison's blog posts) and I added a Custom Action to everyone's Welcome (personal actions) menu.

    Ehsan,
    For one of my clients, I wrote a windows service that, 80 days after their last password change, emails users a reminder to change their password.

    ReplyDelete
  3. Hi Michael,

    I did the same as you did so I guess great minds think alike? ;)

    @Eshan,
    sorry for noticing your comment this late, but I posted a piece of code about a year ago that does exactly what you want. Check it out at http://glorix.blogspot.com/2007/04/password-reminder-updated.html

    ReplyDelete
  4. Robin

    I implemented your web part. But whenever user try to change it. He is getting following error
    ------------
    An unexpected error has occurred. Web Parts Maintenance Page: If you have permission, you can use this page to temporarily disable Web Parts or remove personal settings. For more information, contact your site administrator.
    ---------

    Can you suggest what wrong I did? I also test the user proviliges, they have contribute accress.

    Please help me out asap?

    ReplyDelete
  5. Does it work if you try to change it?

    ReplyDelete
  6. Page is loaded with out any problem, Actually enduser want to change his password, so after entering of old and new password, whne user click on "Change Password" link, he will
    ------------
    An unexpected error has occurred. Web Parts Maintenance Page: If you have permission, you can use this page to temporarily disable Web Parts or remove personal settings. For more information, contact your site administrator.
    ---------

    Let me know what do I need to check?

    ReplyDelete
  7. I am experiencing the same issue as Ash. I am getting the same error whenever anyone tries to change their password. I have looked over the logs and the error is not showing up. I even tried this on the administrator account to make sure this was not an access issue. No luck so far.

    Just checking in to see if you had any suggestions.

    Thanks!

    ReplyDelete
  8. I have the same problem and no idea how to fix it. HELP!!!

    ReplyDelete
  9. Well I guess there is something wrong with your LDAP query to the server. Probably the webpart runs into an error which I haven't covered in my try/catch blocks.

    Can you make sure your LDAP path is properly set?

    Or could you post your modification of the webpart?

    ReplyDelete
  10. What about this Interop.ActiveDs.dll I mean how to inlude this in the package?? maybe this is the solution to the problem.

    ReplyDelete
  11. Well you can add .dll's when you package up your webpart using tools like wspbuilder or stsdev.

    Another approach is to place the Interop.ActiveDs.dll into the BIN folder of the webapplication on your webserver (together with the .dll of your webpart)

    ReplyDelete
  12. I have the issue down to my LDAP settings. I have my LDAP setup correctly on this server for Forms Authentication. This however is not working for this webpart. I don't understand why it works for Forms Authentication, but not this.

    ReplyDelete
  13. @John,

    so you are using Forms Authentication instead of Windows Authentication? If so, the webpart won't work because it's created to change the Active Directory (=Windows Auth) accounts and not FBA based accounts ;)

    ReplyDelete
  14. No, I was commenting that I had one of out sites setup with Forms Authentication. I am not trying to use the webpart on the site with Forms. I was just explaining that the LDAP query worked for the site with Forms. I am using the same query for the webpart with no luck.

    ReplyDelete
  15. maybe a silly question but what's the setting of the trust level in the web.config? Did you try (or is it already) on Full trust?

    ReplyDelete
  16. I think this is the error that most of the people got:
    Request for the permission of type 'System.DirectoryServices.DirectoryServicesPermission, System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' failed

    ReplyDelete
  17. Free commercially proven password change web part - stylish, localizable, supports multiple domains and forests, includes standard web (non-SharePoint user interface and even supports Microsoft ADAM users. Check it out if you have time.
    More free tools on the way!

    http://www.adselfservicesuite.com/

    Patrick

    ReplyDelete
  18. This comment has been removed by the author.

    ReplyDelete
  19. entry.Invoke("ChangePassword", new object[] { oldpassword.Text.ToString(), newpassword.Text.ToString() });

    The most first run, I was able to reset the user password, but the second time the above line throw an exception.

    I have been doing some research but couldn't find any clue.

    Any help will be appreciated

    ReplyDelete
  20. Hi Randy,

    this could be due to the policy that is set on your domain. One of the settings is 'only change password per 24hrs'.. if this policy is active than that's why get the error messages.

    ReplyDelete
  21. Hi Robin, your webpart is very helpful, but not work normally for me. When a user type incorrectly your passwors, an error message displays on sharepoint site ("...An unexpected error has occurred..."). Another resources work normally and my environment is a sharepoint services.

    Thanks!

    ReplyDelete
  22. Hi Robin,
    Is it possible that you have this web part package somewhere for download? No experience with using the code.

    ReplyDelete
  23. Yes a webpart package would be very nice. Excellent work, thanks!

    ReplyDelete
  24. Well after going through a huge learning curve today I would like to offer some insight..

    If you are reciving the following error

    An unexpected error has occurred. Web Parts Maintenance Page: If you have permission, you can use this page to temporarily disable Web Parts or remove personal settings. For more information, contact your site administrator.

    Make sure your LDAP:// string has the correct domain information. For example if your Domain Controler is abc.local the your LDAP string should look like this LDAP://DC=abc,DC=local.

    I would like to ask this. I noticed that after reseting the password I am able to still log into the WSS site using my old password and my new password, however OWA will only see the new password which is a good thing..

    Anyone have any sugestions.

    Doug

    ReplyDelete
  25. How do you import, insert this code in to your sharepoint portal ?

    ReplyDelete
  26. Hey Robin,

    When I try to add the web part to a page I'm getting the error:

    "Unable to add selected web part(s)

    Change my password: Cannot import this web part. "

    have you run into the same problem? I would really appreciate if you could help me with this.

    THANKS !

    by the way why is the Interop.ActiveDs.dll file required? I don't see any reference to it in the code, except for the very first using statement, actually if you don't add this reference and you remove that line it will compile fine.

    ReplyDelete
  27. I was using the SharePoint SmartPart template to deploy the web part:

    http://www.codeplex.com/smarttemplates

    and for some reason it wouldn't work, I kept getting the error:

    "Unable to add selected web part(s)

    Change my password: Cannot import this web part. "

    So I decided to do it manually following this great article:

    http://www.codeguru.com/csharp/.net/net_asp/webforms/article.php/c12293__1/

    And it worked like a charm.

    Great work man.

    Thanks for sharing.

    ReplyDelete
  28. I am new to creating webparts but somewhat familiar with coding. I have compiled the webpart to my website using the provided code with success. The webpart shows up when I view all webparts and my new webpart name is displayed. When I try to view or add the webpart, SP displays an error message, can not import webpart. Could someone help me understand why and possibly how to fix this issue?

    ReplyDelete
  29. Hello Robin

    I am have implemented ADAM AUthentication on my localserver and installed ADAM. I am using directory entry as

    DirectoryEntry entry = new DirectoryEntry("LDAP://localhost:389/CN=Users,OU=ADAMTest,O=ADAM,C=US", strLoginName, oldpassword.Text.ToString(), AuthenticationTypes.None);

    and rest of the code as you mentioned. I am testing webpart directly on MOSS server

    Now whenever I change the code I am getting following error, please let me know whats wrong I am doing? Please advise, it knocking my head too much, I tried so many things but nothing clicked
    *----
    User Password Change Error: at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail) at System.DirectoryServices.DirectoryEntry.Bind() at System.DirectoryServices.DirectoryEntry.get_AdsObject() at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne) at System.DirectoryServices.DirectorySearcher.FindAll() at ADAMPasswordChange.ADAMPassword.btn_Click(Object sender, EventArgs e) Logon failure: unknown user name or bad password.
    Logon failure: unknown user name or bad password.
    *----

    ReplyDelete
  30. Hey Ani,

    I had a similar problem and I solved it removing the last parameter (AuthenticationTypes.None) of the DirectoryEntry constructor.

    Let us know if it helped.

    Regards.

    ReplyDelete
  31. Hello Ulise

    I made the changes, but still getting same error. Here is my code
    *-----
    DirectoryEntry entry = new DirectoryEntry("LDAP://localhost:389/CN=Users,OU=ADAMTest,O=ADAM,C=US", strLoginName, oldpassword.Text.ToString());
    output.Text += " - Directory Entry 2";
    DirectorySearcher search = new DirectorySearcher(entry);
    search.Filter = "(SAMAccountName=" + strLoginName + ")";
    search.SearchScope = SearchScope.Subtree;
    search.CacheResults = false;

    SearchResultCollection results = search.FindAll();
    output.Text += strLoginName + "\n" + strUserName + "\n" + strMyuser + "\n" + oldpassword.Text.ToString() + "\n" + newpassword.Text.ToString() + "\n";
    if (results.Count > 0)
    {
    foreach (SearchResult result in results)
    {
    try
    {
    entry = result.GetDirectoryEntry();
    }
    catch (Exception error) { output.Text += error.Message.ToString(); }

    }

    try
    {
    entry.Invoke("ChangePassword", new object[] { oldpassword.Text.ToString(), newpassword.Text.ToString() });
    entry.CommitChanges();
    output.Text += "Password is changed";
    }
    catch (Exception)
    {
    output.Text += "Password couldn't be changed due to restrictions";
    }
    }


    }
    catch (Exception error)
    {
    output.Text += "User Password Change Error:\n" + error.StackTrace.ToString() + "\n" + error.Message.ToString();
    output.Text += error.Message.ToString();
    }

    *------

    Please advise.

    ReplyDelete
  32. Hello Ulises

    One more thing I forgot to mention that I am using this for FBA. It seems this web part is not for FBA. Please suggest me an alternate option/logic, if it is not for FBA.

    ReplyDelete
  33. Well.. it specifically says "AD Change Password webpart" so any FBA (like ADAM) is not really supported in this webpart.

    ReplyDelete
  34. Ani,

    it might be a dumb question, but have you checked that your ldap path is correct?

    Since I didn't find a suitable location to put this type of webpart I grabbed Robin's code and slightly modified it to create a stand alone page which I later added it to our sharepoint site (with the same layout of course), and it works fine.

    Let me know if you want to look at it and I can send you the whole project in a zip file.

    ReplyDelete
  35. Hello Ulises

    Can you please let me know how to check path is correct?

    Secondly FYI, when you install ADAM, you will also get some VB script. I tested the setpassword.vbs, it works perfectly on server using same path.

    Anyway I may be wrong, let me know how to check path?

    ReplyDelete
  36. Hello Ulises/Robin

    Please review following VBS, I tested on commaond prompt as

    LDAP://localhost:389" CN=inetuser1,O=ADAM, OU=ADAMTest,C=US newPassword123

    and it is working without any problem

    *-----Setpass.vbs
    ''
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Option Explicit

    Public Const ForReading = 1
    Public Const ForWriting = 2
    Public Const ForAppending = 8

    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Sub LogMessage() - writes a message to the screen and logfile
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Sub LogMessage(fsOut, Msg)
    WScript.Echo(msg)
    fsOut.WriteLine(msg)

    End Sub


    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' main()
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Dim oArgs
    Dim oOpenDsObject
    Dim oUser
    Dim szProvider
    Dim szNewPass
    Dim szUserName
    Dim szAdmin
    Dim szAdminPass
    'Dim szLogFile
    Dim fs
    Dim fsOut
    Dim lngError

    'On Error Resume Next

    'Stop

    Set oArgs = WScript.Arguments
    If (oArgs.Count <> 3) Then
    WScript.Echo "usage: SetPassword <-Provider-And-Server-> <-UserSuffix-> <-NewPass->"
    Wscript.Echo "For example, SetPassword LDAP://localhost:389 CN=adamuser,OU=adamou,c=us NewPassword"
    Else
    szProvider = oArgs(0)
    szUserName = oArgs(0) & "/" & oArgs(1)
    szNewPass = oArgs(2)
    End If
    Wscript.Echo oArgs(0) & "/" & oArgs(1) & "/" & oArgs(2)
    Set oUser = GetObject(szUserName)

    'set special dsheuristics bit in order to be able to set password without requiring SSL
    Dim searchobj, objdse, config

    'Get the configuration naming context
    set objdse = GetObject(oArgs(0) & "/RootDSE")
    config = objdse.get("ConfigurationNamingContext")
    set objdse = nothing


    Set searchobj = GetObject(szProvider & "/CN=Directory Service,CN=Windows NT,CN=Services," & config )

    searchobj.Put "dsheuristics", "0000000001001"
    searchobj.SetInfo


    ' Now set the password
    oUser.Put "userpassword", szNewPass
    oUser.SetInfo


    wscript.Echo "Setting the Password was successful"
    *---------

    ReplyDelete
  37. I run above mentioned script as on command prompt in following syntex

    setpass LDAP://localhost:389" CN=inetuser1,O=ADAM, OU=ADAMTest,C=US newPassword123

    ReplyDelete
  38. "Setting" a password is very different from "changing" a password. The key difference is that "setting" a password is done by using the domain admin privilege. And changing a password is changing your own password (just like you do in Windows (Ctrl+Alt+Del > Change Password). So please note that there are key differences between invoking the 'change' and the 'set'.

    ReplyDelete
  39. I have implemented the web part and installed on a web server (that is also a domain controller) with the Password couldn't be changed due to restrictions error. Can I proceed?
    Pasquale

    ReplyDelete
  40. Hi Pasquale,

    yes you can proceed, just take note of all the password complexity requirements of your domain controller.

    Robin

    ReplyDelete
  41. Hi Robin,
    the only password requirement is the minimum length set to 3 characters. But I specify password of 8-9 characters with some digits.
    Pasquale

    ReplyDelete
  42. What about password history? Or the option that you can only change your password one time a day?

    ReplyDelete
  43. This is awesome, great post! I was told that this couldn't be done, so your solution is very helpful

    ReplyDelete
  44. I just finished up essentially the same thing - although not based on this code...

    http://armsinfragilehands.blogspot.com/2008/11/password-reset-web-part.html

    ReplyDelete
  45. very great blog! I searched the answer for months with no luck! your blog definitely helps me a lot!
    By the way I also got a tool called sharepoint password change http://www.sharepointboost.com/passwordchange.html
    which is great! I mean with this tool you can send notification email to users when their passwords expired! Who you do this?
    Thanks for you blog again!

    ReplyDelete
  46. Right now I just wandering with your solution could I send some notifications when some passwords are expired? Because right now I am using a web part tool called sharepoint password change which offer such feature.

    Thanks again of your blog.

    ReplyDelete
  47. Robin, great idea and will save lots of hassles.

    I'm not a coder nor techie person, so could you please explain where to put the code for change password webpart? I created a web part zone and then pasted the code between the tag bits, but nothing happened. Now, I'm sure I am missing some big steps in there about linking to a database etc and creating a 'blank' webpart.

    ReplyDelete
  48. Hello there

    I got the source code for AD Change Password WebPart‏ from codeplex and also downloaded and installed WSPBuilder to deploy the webpart onto sharepoint. i can build and deploy the webpart successfully but I cant see this webpart nowhere on sharepoint. So I want to put this webpart somewhere on SharePoint site to let users change their AD password, I have looked for the webpart into Site Collection etc but not there. Could you please tell me am I missing something?

    any help really appreciated

    Kind Regards,

    Saleem

    ReplyDelete
  49. hi

    this is realy gr8

    i use this and it is working fine in my case.

    but currently i m facing some problem like.

    Users creating problem:
    user detail in AD
    first name: share
    last name: point
    user login name: share

    so user login name is "share" and full name is "share point"

    it is not changing password of this type of user.

    if my login name and full name is similar then no problem..

    any idea.

    ReplyDelete
  50. Hello,

    does this work with SharePoint 2010?

    Thank you!

    Best wishes,
    Marko

    ReplyDelete
  51. Dera Rabin,

    I am facing a problem while changing the user password, it showing the error message (Password couldn't be changed due to restrictions), Kindly say me whats the problem

    ReplyDelete
  52. Dera Rabin,

    I am facing a problem while changing the user password, it showing the error message (Password couldn't be changed due to restrictions), Kindly say me whats the problem

    ReplyDelete
  53. I m very new to MOSS 2007.
    We have 30 users in active directory. I want to implement password policy for this moss users.

    But i dont know how to link active directory to moss site..
    can anyone help me with BASICS...

    ReplyDelete
  54. i have one question how to change password in sharepoint

    ReplyDelete
  55. Hi thanks for this, we also found a way but we had to pay for it, but it gave a few more options.. but wasnt free..
    www.qipoint.com .. dunno if that will work for u but worked for us and so far has great support

    ReplyDelete
  56. Hi Robin

    Thank you so much for this post. I am a complete newbie to sharepoint and thus was wondering that if in this webpart a link to a password reset portal URL be included? Thus if an user has an impending password expiry, they get a prompt and a link to the URL to reset their password. Can this be done? Will their be major changes in the code?
    Regards

    ReplyDelete
  57. Wonderful. Made a few changes to the code for myself, specifically the LDAP, and it works like a charm. My test accounts are registering the new passwords, and the visual errors are showing up correctly.

    Thanks for your hard work. You have saved my colleagues and I a number of hours.

    ReplyDelete
  58. Thanks for sharing Active directory password reset tool tips. for more info i rfer cion systems Active directory password reset tool in USA.

    ReplyDelete