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 ;)

56 comments:

Ehsan said...

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

Michael B said...

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.

Robin Meuré said...

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

Ash said...

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?

Robin Meuré said...

Does it work if you try to change it?

Ash said...

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?

John said...

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!

Anonymous said...

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

Robin Meuré said...

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?

Anonymous said...

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

Robin Meuré said...

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)

John said...

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.

Robin Meuré said...

@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 ;)

John said...

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.

Robin Meuré said...

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?

Ed Bagsik said...

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

Anonymous said...

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

Randy said...
This comment has been removed by the author.
Randy said...

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

Robin Meuré said...

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.

Tarcisio said...

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!

Jason Powell said...

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

Colin said...

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

Doug said...

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

Anonymous said...

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

Ulises said...

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.

Ulises said...

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.

Anonymous said...

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?

Ani said...

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.
*----

Ulises said...

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.

Ani said...

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.

Ani said...

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.

Robin Meuré said...

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

Ulises said...

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.

Ani said...

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?

Ani said...

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"
*---------

Ani said...

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

Robin Meuré said...

"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'.

Anonymous said...

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

Robin Meuré said...

Hi Pasquale,

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

Robin

Anonymous said...

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

Robin Meuré said...

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

John Currah said...

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

Thomas Burke Holland said...

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

Anonymous said...

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!

itguy said...

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.

Adrienne Gross said...

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.

Anonymous said...

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

Anonymous said...

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.

Marko said...

Hello,

does this work with SharePoint 2010?

Thank you!

Best wishes,
Marko

Anonymous said...

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

Anonymous said...

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

ganesh said...

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...

janaki said...

i have one question how to change password in sharepoint

Chris said...

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

Anonymous said...

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