Tuesday, December 02, 2008

MOSS WebPart to change user password using WinNT

Last week I was faced to following problem: I drive a temporary MOSS Website on a standalone box for a half dozen of external users. Every user has an own local (!) account on the box since there's no domain. It does work well since MOSS supports WinNT authentification.

The local administrator is responsible to create users and provide them with initial passwords. Users can now login on Website. Immediately come the questions like: "How can I change my initial password? I forgot my password, can you reset it?"

As the one and only administrator of the box I can do it only over the weekend, when I get physical access to the machine. It is a little bit annoying for users as well as for me. So I dreamed about self-service for users: just change your own password via dedicated MOSS web page.

After a couple of minutes Web surfing I've understood: there are numerous approaches to change a user password for AD based MOSS farms. Unfortunately they won't work for WinNT authentification.

So I decided to write my own Web part helping the currently logged in user to change her/his password. Additionally there must be an option for administrator to set password for any user, if he/she forgot it.

Thanks to the MOSS Web part framework and .NET Framework at all, the work was done in couple of hours:

  • Implement a routine to check if the currently logged in user belongs to local administrators group.

  • Override CreateChildrenControls() to build the UI for desired Web part. We need:
    • textbox for username
      • set this box to read-only if currently logged in user is not a local admin (simple users cannot change passwords of other users)
      • let the local admin to put any user name here to be able to change password for any other user
    • textbox for old password
    • textbox for new password
    • textbox for new password repeated (to avoid typos in new password)
    • button to invoke change password operation
    • labels for all the textboxes to guide the user input
    • a control to display result of the operation (I used another label control)
    • a couple of literals with line break to align UI elements

  • Implement click handler for the button invoking the password change. Here's some logic behind the scenes: if the content of the username textbox is the same as login name of the currently logged in users, the change password routine should be invoked. The old password and new password are required for changing the password.
    If the content of the username textbox differs from the login name of the currently logged in users, the set password routine should be invoked (currently logged in user is a local administrator and wants to set a forgotten password for another Web site user). Sounds more complicated as implementation.

  • Both change password and set password routines are easily to invoke using built-in .NET support for Active Directory: event using WinNT as authentification provider. System.DirectoryServices is the namespace, and DirectoryEntry is the class one needs to use to achieve the goal.
    The standard ADSI scenario: Find, Bind, Edit, Save – does work here. To set or change password for a user, the user object must be bound. The connection string has usually the form: "WinNT ://<servername>/<username>,User", where servername is the authentification NT machine name, and username is the user's login name.
    Both names can be passed out of username obtained from identity of current thread.

  • Being skeptical about trouble-free work of the part, one may want a rudimentary tracing functionality just dropping tracing messages in a local text file. This functionality can help on troubleshooting but must be flexible enough to be turned on or off.

  • In common there's not much to configure the Web part. Nevertheless two configuration options were implemented in the Web part:
    • Provider connection string (standard value "WinNT://<servernbame>/<username>,User" as described above)
    • Pathname of the log file: additionally as toggle for tracing – no tracing on null or empty log file pathname

    Both options were implemented as Web part editable parameters to provide the Web site administrator with comfortable configuration integrated into standard MOSS Web page edit mode.

    That's all. Check out the code and feel free to ask for more information (e. g. how to build a MOSS solution with this Web part, how to localize etc.


    Enjoy!


    WebPart as .wsp file available on request.

    Code:


using System;

using System.Diagnostics;

using System.DirectoryServices;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using Microsoft.SharePoint.WebControls;

using System.Security.Principal;

using System.Threading;



namespace MBecker.SharePoint.Web

{


public
class
MBChangePasswordWebPart : WebPart

{


///
<summary>


/// WebPart title


///
</summary>


public
const
string Title = "Web Part to help users to change their passwords.";



///
<summary>


/// Logfile pathname


///
</summary>


private
string logFileName = "";



///
<summary>


/// Directory entry string to access user object


///
</summary>


private
string userEntryString = "";



///
<summary>


/// WebPart property: Provider connection string.


/// To be configured in webpart modification mode.


///
</summary>

[WebBrowsable]

[WebDisplayName("Provider connection string")]

[WebDescription("Provider connection string to get the user object for password changes; {0} is the placeholder for user login, {1} is the placeholder for domain/server name.")]

[Personalizable(PersonalizationScope.Shared)]


public
string UserEntryString

{


get

{


return userEntryString;

}


set

{

userEntryString = value;

}

}



///
<summary>


/// WebPart property: Logfile pathname.


/// To be configured in webpart modification mode.


///
</summary>

[WebBrowsable]

[WebDisplayName("LogFile Pathname")]

[WebDescription("Quick switch to toggle tracing on/off; empty logfile pathname assumes no tracing required")]

[Personalizable(PersonalizationScope.Shared)]


public
string LogFilename

{


get

{


return logFileName ;

}


set

{

logFileName = value;

}

}




///
<summary>


/// Mastering outlook of the WebPart with required child controls, required.


///
</summary>


protected
override
void CreateChildControls()

{


if (Page.IsPostBack)

{


}


else

{


}



// Add webpart controls


try

{


// Label for username


Label labelUsername = new
Label();

labelUsername.Text = Resource.labelUser;

labelUsername.CssClass = "changePasswordLabel";

Controls.Add(labelUsername);



Literal br1 = new
Literal();

br1.Text = "<br/>";

Controls.Add(br1);



// Textbox to enter username


// will be displayed as readonly if current user is not an local administrator


TextBox textBoxUsername = new
TextBox();

textBoxUsername.Text = System.Threading.Thread.CurrentPrincipal.Identity.Name;

textBoxUsername.ReadOnly = !IsAdminMode();

textBoxUsername.CssClass = "changePasswordTextBox";

textBoxUsername.ID = "textBoxUsername";

Controls.Add(textBoxUsername);



Literal br2 = new
Literal();

br2.Text = "<br/>";

Controls.Add(br2);



// Label for old password


Label labelOldPassword = new
Label();

labelOldPassword.Text = Resource.labelOldPassword;

labelOldPassword.CssClass = "changePasswordLabel";

Controls.Add(labelOldPassword);



Literal br3 = new
Literal();

br3.Text = "<br/>";

Controls.Add(br3);



// Password textbox to enter the old password


PasswordTextBox textBoxOldPassword = new
PasswordTextBox();

textBoxOldPassword.ID = "textBoxOldPassword";

textBoxOldPassword.CssClass = "changePasswordTextBox";

Controls.Add(textBoxOldPassword);



Literal br4 = new
Literal();

br4.Text = "<br/>";

Controls.Add(br4);



// Label for new password


Label labelNewPassword1 = new
Label();

labelNewPassword1.Text = Resource.labelNewPassword1;

labelNewPassword1.CssClass = "changePasswordLabel";

Controls.Add(labelNewPassword1);



Literal br5 = new
Literal();

br5.Text = "<br/>";

Controls.Add(br5);



// Password textbox to enter new password


PasswordTextBox textBoxNewPassword1 = new
PasswordTextBox();

textBoxNewPassword1.ID = "textBoxNewPassword1";

textBoxNewPassword1.CssClass = "changePasswordTextBox";

Controls.Add(textBoxNewPassword1);



Literal br6 = new
Literal();

br6.Text = "<br/>";

Controls.Add(br6);



// Label for new password repeated


Label labelNewPassword2 = new
Label();

labelNewPassword2.Text = Resource.labelNewPassword2;

labelNewPassword2.CssClass = "changePasswordLabel";

Controls.Add(labelNewPassword2);



Literal br7 = new
Literal();

br7.Text = "<br/>";

Controls.Add(br7);



// Passowrd textbox to enter new password repeated


PasswordTextBox textBoxNewPassword2 = new
PasswordTextBox();

textBoxNewPassword2.ID = "textBoxNewPassword2";

textBoxNewPassword2.CssClass = "changePasswordTextBox";

Controls.Add(textBoxNewPassword2);



Literal br8 = new
Literal();

br8.Text = "<br/><br/>";

Controls.Add(br8);



// Button to invoke password change


Button buttonSetPassword = new
Button();

buttonSetPassword.Text = Resource.buttonSetPassword;

buttonSetPassword.Click += new
EventHandler(buttonSetPassword_Click);

buttonSetPassword.ID = "buttonSetPassword";

buttonSetPassword.CssClass = "changePasswordButton";

Controls.Add(buttonSetPassword);



Literal br9 = new
Literal();

br9.Text = "<br/><br/>";

Controls.Add(br9);



Label labelResult = new
Label();

labelResult.ID = "labelResult";

labelResult.CssClass = "changePasswordResultLabel";

Controls.Add(labelResult);


}


catch (Exception ex)

{


// Adding child controls fired an exception


Debug.Assert(false, Resource.exceptionCreateChildControls + ex.Message + "\r\n" + ex.StackTrace);

trace(Resource.exceptionCreateChildControls + ex.Message + "\r\n" + ex.StackTrace);

}



base.CreateChildControls();


}



///
<summary>


/// Button click handler, invokes change/set password


///
</summary>


///
<param name="sender">Button object</param>


///
<param name="e">Click event argumets</param>


void buttonSetPassword_Click(object sender, EventArgs e)

{


string result;


try

{

((Button)this.FindControl("buttonSetPassword")).Enabled = false;

((Label)this.FindControl("labelResult")).Text = "";


try

{


bool bSetPassword = false;


string userName = ((TextBox)this.FindControl("textBoxUsername")).Text;



// Check if set or change password operation should be invoked


// change password - to change password for currently logged in user


// set password - to change password for username entered in username textbox


// (only if currently logged in user is a local admin)

bSetPassword = (userName != System.Threading.Thread.CurrentPrincipal.Identity.Name);


string serverName = "";


if (userName.IndexOf('\\') != -1)

{


// Parse user name to split server name and user name

serverName = userName.Substring(0,userName.IndexOf('\\'));

userName = userName.Substring(userName.IndexOf('\\')+1);

}


if (!bSetPassword)

{


// Change password for currently logged in user

result = changePassword(

serverName,

userName,

((PasswordTextBox)this.FindControl("textBoxOldPassword")).Password,

((PasswordTextBox)this.FindControl("textBoxNewPassword1")).Password,

((PasswordTextBox)this.FindControl("textBoxNewPassword2")).Password

);

}


else

{


// Set password for a username entered in textbox

result = setPassword(

serverName,

userName,

((PasswordTextBox)this.FindControl("textBoxNewPassword1")).Password,

((PasswordTextBox)this.FindControl("textBoxNewPassword2")).Password

);

}

}


catch (Exception ex)

{


Debug.Assert(false, Resource.exceptionSetChangePassword1 + ex.Message + "\r\n" + ex.StackTrace);

trace(Resource.exceptionSetChangePassword1 + ex.Message + "\r\n" + ex.StackTrace);

result = ex.Message + "\r\n" + ex.StackTrace;

}

((Label)this.FindControl("labelResult")).Text = result;

((Button)this.FindControl("buttonSetPassword")).Enabled = true;

}


catch (Exception ex)

{


Debug.Assert(false, Resource.exceptionSetChangePassword2 + ex.Message + "\r\n" + ex.StackTrace);

trace(Resource.exceptionSetChangePassword2 + ex.Message + "\r\n" + ex.StackTrace);

}

}



///
<summary>


/// Check if the currently logged in user is a local admin


///
</summary>


///
<returns>True if the currently logged in user is a local admin, false otherwise</returns>


private
bool IsAdminMode()

{


bool bRet = false;


string groupAdministrators = "Administrators";



//if (System.Threading.Thread.CurrentPrincipal.Identity.Name.IndexOf("mbecker") != -1) bRet = true; // :-)


AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

bRet = Thread.CurrentPrincipal.IsInRole(groupAdministrators);

trace(


String.Format("{0} is " + (bRet?"":" not ") + "in role {1}.",


Thread.CurrentPrincipal.Identity.Name,

groupAdministrators

)

);


return bRet;

}



///
<summary>


/// Change user password


///
</summary>


///
<param name="serverName">Server name</param>


///
<param name="userName">User Loginname</param>


///
<param name="oldPassword">Old password</param>


///
<param name="newPassword1">New password</param>


///
<param name="newPassword2">New password repeated</param>


///
<returns>State string to display on webpart</returns>


private
string changePassword(


string serverName,


string userName,


string oldPassword,


string newPassword1,


string newPassword2)

{


string result = Resource.textPasswordChanged;


if (newPassword1 != newPassword2)

{


// new password and new password repepated do not match, break operation

result = Resource.testPasswordNotChanged + Resource.exceptionPasswordDoNotMatch;

}


else

{


try

{


// Change user password as seen in numerous ADSI samples


DirectoryEntry myDirectoryEntry;


string directoryEntryString = String.Format(UserEntryString, userName, serverName);

trace(directoryEntryString);

myDirectoryEntry = new
DirectoryEntry(directoryEntryString);


string[] passwords = new
string[2];

passwords[0] = oldPassword;

passwords[1] = newPassword1;

myDirectoryEntry.Invoke("ChangePassword", passwords);

}


catch (Exception ex)

{


// Change password failed

result = Resource.testPasswordNotChanged + ex.StackTrace;


while (ex != null)

{

trace(ex.Message);

trace(ex.StackTrace);

ex = ex.InnerException;

}

}

}


return result;

}



///
<summary>


/// Set user password


///
</summary>


///
<param name="serverName">Server name</param>


///
<param name="userName">User Loginname</param>


///
<param name="newPassword1">New password</param>


///
<param name="newPassword2">New password repeated</param>


///
<returns>State string to display on webpart</returns>


private
string setPassword(


string serverName,


string userName,


string newPassword1,


string newPassword2)

{


string result = Resource.textPasswordChanged;


if (newPassword1 != newPassword2)

{


// new password and new password repepated do not match, break operation

result = Resource.testPasswordNotChanged + Resource.exceptionPasswordDoNotMatch;

}


else

{


try

{


// Set user password as seen in numerous ADSI samples


DirectoryEntry myDirectoryEntry;


string directoryEntryString = String.Format(UserEntryString, userName, serverName);

trace(directoryEntryString);

myDirectoryEntry = new
DirectoryEntry(directoryEntryString);

myDirectoryEntry.Invoke("SetPassword", newPassword1);

}


catch (Exception ex)

{


// Set password failed

result = Resource.testPasswordNotChanged + ex.StackTrace;


while (ex != null)

{

trace(ex.Message);

trace(ex.StackTrace);

ex = ex.InnerException;

}

}

}


return result;

}



///
<summary>


/// Trace diagnostic information in configured log file.


/// Ensure access rights to the file/folder to avoid exceptions due to


/// file access failures


///
</summary>


///
<param name="msg">Trace message string</param>


private
void trace(string msg)

{


if (!String.IsNullOrEmpty(LogFilename))

{


// Trace message if a logfile specified


try

{

System.IO.StreamWriter sw = new System.IO.StreamWriter(LogFilename, true);

sw.WriteLine(DateTime.Now.ToString() + "\t" + msg);

sw.Flush();

sw.Close();

}


catch (Exception ex)

{


Debug.Assert(false, String.Format(Resource.exceptionTrace, msg) + ex.Message + "\r\n" + ex.StackTrace);

}

}

}

}

}

Thursday, August 28, 2008

Internet Explorer 8 Beta 2

What a change!!! Never saw IE so speedy and useful !!!

The absolute "must have".

Download here.

Enjoy!

Tuesday, August 19, 2008

Detecting hardware memory problems

Last week I've got a new PC for evaluation of W2K8 Server - especially Hyper V features. Unfortunately, two days ago the box started to show me BSOD approximately once a hour. All the available out-of-box checks didn't show valuable results. Microsoft error reporting displayed on each new start after crash, the report is corrupt. Analyzing of numerous dump files didn't show any regularity either.

Only usage of Microsoft Memory Diagnostics helped to detect memory problems:
http://oca.microsoft.com/en/windiag.asp

This free tool was the only solid technical witness for computer store to change the bad memory modules.

I would highly recommend to burn this bootable diagnostics CD and keep it in the "emergency box" to be able to detect memory problems quickly.

Enjoy!

Friday, August 15, 2008

Windows Live Writer for bloggers

This blog entry I created using Windows Live Blog Writer:

http://get.live.com/writer/overview

Must have for bloggers.

Enjoy!

Thursday, August 07, 2008

Cannot scan to a SMB location on Vista (Part II)

Investigating scanning to SMB problem - using RICOH multi-function device and VISTA Home Premium edition - found out, the proposed solution is no always enough.

Trying to send scanned page to a shared folder on VISTA Home Premium box still returned error "connection error". Other computer in the same net segment can easily create files and folder on this share but RICOH cannot.

Installed wireshark on the VISTA box and recorded network conversation between RICOH and VISTA. Following fragment was the key to found error cause:

No. Time Delta Source Destination Protocol Info SrcPort DstPort HwSrc HwDst Length

2 21:16:19.909854 0.000099 192.168.2.105 RNPC5354D NBNS Name query response NBSTAT 137 61929 00:1d:92:29:55:ec 00:00:74:c5:35:4d 253

Frame 2 (253 bytes on wire, 253 bytes captured) Ethernet II, Src: Micro-St_29:55:ec (00:1d:92:29:55:ec), Dst: Ricoh_c5:35:4d (00:00:74:c5:35:4d) Internet Protocol, Src: 192.168.2.105 (192.168.2.105), Dst: RNPC5354D (192.168.2.102) User Datagram Protocol, Src Port: netbios-ns (137), Dst Port: 61929 (61929) NetBIOS Name Service

Transaction ID: 0x0000

Flags: 0x8400 (Name query response, No error)

Questions: 0

Answer RRs: 1

Authority RRs: 0

Additional RRs: 0

Answers

*<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00>: type NBSTAT, class IN

Name: *<00><00><00><00><00><00><00><00><00><00><00><00><00><00><00>

Type: NBSTAT

Class: IN

Time to live: 0 time

Data length: 155

Number of names: 6

Name: SCH<9a>R<00> (Workstation/Redirector)

Name flags: 0x400 (B-node, unique, active)

Name: RA<00> (Workstation/Redirector)

Name flags: 0x8400 (B-node, group, active)

Name: SCHsR<20> (Server service)

Name flags: 0x400 (B-node, unique, active)

Name: RA<1e> (Browser Election Service)

Name flags: 0x8400 (B-node, group, active)

Name: RA<1d> (Local Master Browser)

Name flags: 0x400 (B-node, unique, active)

Name: <01><02>__MSBROWSE__<02><01> (Browser)

Name flags: 0x8400 (B-node, group, active)

Unit ID: 00:1d:92:29:55:ec

The NETBIOS name of the VISTA box was using german umlaute characters: SCHÃœR. These characters were wrong interpreted by RICOH (seems to be, the built-in networking communication software cannot process localized characters - only english character set).

Changed the name to MYPC - it worked immediately.

One more remark: localization of VISTA is smart. I'd say - too smart. When I tried to the name of the box to SCHUER, VISTA keeped box name as SCHÜR instead - automatically transform UE letters combination to an Ü according regional settings.

So, never use local language special characters if you want that networking devices speaking with shares on your machine over NETBIOS can connect to them.

Enjoy!

Access privileges troubles while using STSADM

Being promoted to MOSS Farm administrator in Central Administration Application still doesn't mean one may freely use also STSADM utility on this farm.
Promotion to farm administrator registers a user for use MOSS config database but not the Admin Content database. You need access to Admin Content database to be able to use STSADM - at least dbdatareader and dbdatawriter role.

Unfortunately, there are number of operations not available from Central Administration, but highly required to perform effective farm administration. One sample is adding a MOSS solution to the farm: you cannot do it from CA - only STSADM -o addsolution helps.

So, as far as you consider to use STSADM utility to manage your farm - be sure you have enough privileges for MOSS SQL database.

Enjoy!

Tuesday, June 10, 2008

Missing annotations in Microsoft Document Imaging

User of Microsoft Office 2003 may miss annotations functionality in Mircosoft Document Imaging: the toolbar and controls are here, but nothing works. Even more: the annotations contained in a TIF document are no shown.

The reason: Mirosoft Handwriting Component is not installed.
Refer to http://support.microsoft.com/kb/828507/en-us, install Mirosoft Handwriting Component and use annotations as expected.

Enjoy!

Thursday, May 29, 2008

Cannot scan to a SMB location on Vista

Many scanners offer a possibility to scan to a network location. There are different possible protocols to transfer scanned files over the wire. Most popular are SMB and FTP. SMB uses UNC notation to scan to a network share on your PC (home version) or a server.
While it works fine for Windows XP you may encounter problems after switching to Vista. Same network, same share name, same IP address, same access privileges, but... the scanner reports "connection error" or "connection unavailable".

One of popular reasons is the difference in handling of SMB network packets between Windows XP and Vista.

What now? Check the MS KB article http://support.microsoft.com/kb/935755/, download the hotfix (available on the top of the page), install on your Vista, restart the box and scan the page again. Should work...

Enjoy!

Thursday, May 08, 2008

MOSS custom site templates not visible

Sometimes it is very helpful to save a freshly created MOSS site as a template to install it later on another platform. Once uploaded to site templates library, you may still miss the templates in the list of custom site templates.

Why? Because the template language differs from site collection language.

What to do? Change template language to be same as site collection language.

How? Use manual instructions (currently in german, but you may try to translate in in english online )

http://sharepoint-dms.com/blog/Lists/Posts/Post.aspx?ID=19

or

use a freeware tool .STP language converter

http://www.kwizcom.com/ContentPage.asp?PageId=165

Enjoy!

Tuesday, April 29, 2008

Unexpected error connecting Report Viewer in MOSS WebPart page

If you plan to use Report Viewer WebPart (from SQL Server Reporting Services) in your WebPart pages dynamically (i. e. use this WebPart as connected WebPart - receiving Report Url): DO NOT FORGET TO INSTALL THE WP PACK GLOBALLY (-globalinstall option)!!!

Refer to: http://technet.microsoft.com/en-us/library/ms159772.aspx

STSADM.EXE -o addwppack -filename "C:\ Program Files\Microsoft SQL Server\90\Tools\Reporting Services\SharePoint\RSWebParts.cab" -globalinstall
Otherwise you will get always an unexpected error on exit edit mode.

Enjoy!

Monday, January 28, 2008

Windows degraded performance due to PIO IDE channel mode

One day you feel your computer becomes slower. Adding RAM won't help.
There may be many different reasons: one of them is - IDE channel serving your IDE ATA or ATAPI disks is running in PIO mode instead of DMA.
Symptoms: start Process Explorer (made by ex-Sysinternals, nowadays Microsoft guys Russinovitch & Co. http://technet.microsoft.com/en-gb/sysinternals/bb896653.aspx ) and monitor CPU usage. If you see, that "Interrupts" pseudo-task is using significant - over 30% - CPU time, follow the MS Knowledge Base article http://support.microsoft.com/kb/817472.
Enjoy!