WinBatch Tech Support Home

Database Search

If you can't find the information using the categories below, post a question over in our WinBatch Tech Support Forum.

TechHome

ADSI
plus

Can't find the information you are looking for here? Then leave a message over on our WinBatch Tech Support Forum.

Synchronize Domain and Local Password Issue


Question:

I have written a script that changes the password of a dial up user on the domain and when I try to change the cached local password I get an error.

I use dsSetPassword to successfully change my domain password.

I am trying to use wntChgPswd("","",oldpass,newpass) to change the locally cached password. However I get Extender Error 562 Invalid Username when this runs. What command should I use to change the locally cached password.

My ultimate goal of this script is to allow the user to change thier domain password over dial-up and keep the cached password synchronized with the domain password.

Here is the contents of my wwwbatch.ini file created in the windows folder:

[WWWNT34I]
LastError=2221 (NetUserChangePassword)
Any help would be appreciated.

Answer:

Error 2221 also indicates the NT user name was not found...

I'm very concerned about how the NT and ADSI extenders are being mixed here with regard to the password changes that are being performed.

Here is my understanding of the problem domain properlyl; please correct me if I'm wrong:

  1. The user is using a WinXP workstation.

  2. The user connects to a domain via a dial-up connection.

  3. The user's logon to a domain user account is performed via dial-up at the GINA logon screen.

  4. After the logon completes, the user desires to change their password.

  5. If the user's password is changed directly in AD, then the locally cached credentials in the user's access-token are now stale and all attempts at accessing network resources fail due to the mismatch in the credentials between the user's current session's access-token and AD.

  6. The function wntChgPswd() is being used in an attempt at getting the locally cached password to be updated to match the one stored in AD. The function wntChgPswd() is failing with a 562 error when it is used in a script with empty string [default] parameter values for the server & username parameters.

Is this correct?

Once I know for certain that I'm understanding the problem domain I'll go into further discussion about what might be going wrong.

User Reply:

The user is using a WinXP workstation and the user connects to a domain via a dial-up connection.

I am using Connection Manager Access Kit(CMAK) to create a custom dial-up entry to launch my winbatch script after a connection is made.

If the password is < threshold the user will be prompted to change their password.

The user's password is changed directly in AD, then the locally cached credentials in the user's access-token are now stale

The function wntChgPswd() is being used in an attempt at getting the locally cached password to be updated to match the one stored in AD. The function wntChgPswd() is failing with a 562 error when it is used in a script with empty string [default] parameter values for the server & username parameters.

Answer:

OK, I'm going to have to research this issue in a bit more detail before I start making recommendations.

The part that really bothers me is making the password change directly in AD such that is bypasses the local workstation. If you used wntChgPswd() to make the change while connected to the network then the updated password would be in both the local cache and back in AD. However, once you change AD and thus invalidate the locally cached credentials, I'm not sure if there is any way to fix things short of logging out locally, and then logging back on such that you use dial-up networking and authenticate via a domain controller so that your domain account credentials get refreshed as part of performing the logon to the local workstation.

What I need to research is whether or not there is any way to get the cached credentials updated w/o the logoff & logon.

And, of course, there's the matter of the 562 error. Given your special situation, there may be an issue with the LSA [Local Security Authority] on the workstation not being able to resolve a SID back to a name or a name to a SID. Underneath it all, the wntChgPswd() function uses 2 different Win32 API functions to perform password reset & change tasks. If it is a reset with a "*UNKNOWN*" password, then NetUserSetInfo() is called and a new password is forcibly set. If you do know the old password [e.g. changing your own password], then NetUserChangePassword() is called. In either case, the server name & username values are not processed at all by the NT extender; they are simply passed directly in to the Win32 API functions "as is". The error 562 is simply how the NT extender translates a Win32 API error code into something more friendly. Given these facts, it makes me think that something in your configuration falls outside of the boundary conditions under which wntChgPswd() was developed, tested & documented.

User Reply:

I am not opposed to changing the script to use only wntChgPswd() if it can change my domain password and locally cached password without being authenticated by a domain controller (logged in using dial-up). Thanks for your help.

Answer:

I'm still looking into it...

But, AFAIK, the underlying NetUserChangePassword() function requires you to be connected to the network to change your password when you are logged on locally using a domain account. It really doesn't care if you're on a LAN or a dial-up connections, the important part is that you're able to communicate with a domain controller when the change is made.

Here's one MS KB article that I ran across that is of some interest. I don't know that it directly applies to this problem, but it does relate to cached credentials & domain resource access problems after establishing a dial-up connection.

More information...

According to Microsoft, the proper sequence to go through to get the cached credentials updated to match the domain credentials is to logon to the local workstation while connected to the domain so that a domain controller may be contacted. Doing this results in the domain controller being used to authenticate the user's credentials rather than using cached credentials, and it results in the local credential cache being updated with fresh information. This is the recommended solution for when the locally cached credentials get out of sync with the user's credentials stored in the domain [e.g. within AD].

Changing the password directly in AD bypasses the mechanisms that are used during a locally initiated password change that would have resulted in updating the locally cached credentials. In that situation, the above mentioned procedure is the only way to re-sync the credentials.

Microsoft states that the function NetUserChangePassword() will update the locally cached credentials when it changes the password in the domain. However, a domain controller must be reachable when this function is called or else it will fail. When you call wntChgPswd() and specify the old username as a value other than "*UNKNOWN*", then NetUserChangePassword() is the Win32 API function that gets called to perform the password change.

There is a caveat with NetUserChangePassword(), though, with regard to the usernames that it can accept. If you omit the server name and/or username when calling NetUserChangePassword(), the function is documented in the Win32 Platform SDK as defaulting to using the current logon domain & logon username. Even if it uses these defaults, the username must meet some naming requirements that may be exceeded in an AD environment. From the Win32 Platform SDK docs, the following requirements are stated:

User account names are limited to 20 characters and group names are limited to 256 characters. In addition, account names cannot be terminated by a period and they cannot include commas or any of the following printable characters: ", /, \, [, ], :, |, <, >, +, =, ;, ?, *. Names also cannot include nonprintable characters in the ranges 1-7, 10-17, 20-27, and 30-37.

Since you're getting an error 562 when calling wntChgPswd(), which indicates that the user was not found, I suspect that perhaps you are logging on with your username specified in the more modern AD-style UPN notation rather than in the NT-style NTLM simple username notation. If your username doesn't meet these naming requirements then NetUserChangePassword() is going to fail and return an error code that the NT extender translates into error 562.

Please confirm whether or not your usernames comply with these account naming requirements. If in doubt, please provide samples here that can be reviewed.

Lacking any further factual information about this problem, I have a strong inclination to think that the NT extender is working properly and that your configuration & usage of password changing methods is creating a problem condition for which Microsoft has clearly described both the cause of the problem and the recommended solution to the problem. If that is in fact the case, then you are going to need to alter your password changing procedures so that they no longer create this problem condition.

User Reply:

I got it to work. The problem was like you said, I was using "" instead of feeding the domain and username to the result = wntChgPswd("domain", "username", currentpass, newpass) function. I removed the part of the script where I changed the password in AD. It now works like a charm. Thank you very much for your help. Here is my final code that could be useful for someone. Remember this has not been debugged.
addextender("wwads34i.dll")
addextender("wwwnt34i.dll")

domain1 = ""
domain2 = ""
;Admin account to perform AD functions
adminln = ""
adminps = ""
;Number of days before passwords expire
passexpire = 45
;Number of days left before password expires to prompt for change
threshold = 10
;Current User logged in
username = environment("username")
verify = @false


;Set AD Credentials
dsSetCredent(adminln, adminps)

;Check for AD existence
ADPath = "LDAP://%domain1%/DC=%domain1%,DC=%domain2%"
if !(dsIsObject(ADPath)) 
goto goodbye
endif

;Get path of Users AD account in AD
errormode(@off)
lDAPPath= dsFindPath(ADPath, "sAMAccountName=%username%")
errormode(@cancel)
lasterr = lasterror()
if (lasterr != 0)
message("Error","Error #%lasterr% occured while finding the username in AD")
goto goodbye
endif

;Is User account < X days from expiration
errormode(@off)
lastset = dsGetProperty(LDAPPath, "pwdLastSet")
errormode(@cancel)
lasterr = lasterror()
if (lasterr != 0)
message("Error","Error #%lasterr% occured while requesting the password expiration date from AD")
goto goodbye
endif
currenttime = TimeYmdHms()
lastset = strfixchars(lastset,"",10)
currenttime = strfixchars(currenttime,"",10)
timedif = TimeDiffDays(currenttime,lastset)
expire = (passexpire - timedif)
if (expire > threshold)
exit
endif

;Prompt User to change password
:ChangePass
Gosub LoadDialog 
Dialogtxt = "Your Password will expire in %expire% day(s) and should be changed immediately. Please enter the information below and choose SET PASSWORD to avoid this notification."
ButtonPushed=Dialog("GetPassword")
while((strlen(newpass) < 6) || (newpass != newpassconfirm) || (currentpass == newpass) || (strlen(currentpass) < 6))
if (currentpass == newpass)
message("ERROR","New Password must be different from Current Password")
else
if (newpass != newpassconfirm)
message("ERROR","Your New Passwords do not match!")
else
if (strlen(newpass) < 6)
message("ERROR","New Password must be at least 6 characters")
else
if (strlen(currentpass) < 6)
message("ERROR","Current Password must be at least 6 characters")
endif
endif
endif
endif
ButtonPushed=Dialog("GetPassword")
endwhile

;Change password
verify = @true
result = wntChgPswd(domain1, username, currentpass, newpass)
while (result != 1)
askline = ("Invalid Current Password", 'Please enter the password you used to login to your machine after you were prompted to "Press Ctrl+Alt+Del":') 
result = wntChgPswd(domain1, username, currentpass, newpass)
endwhile
message("Success","Your Password has been Successfully Changed and is now Valid for %passexpire% Days."
goto goodbye


:Cancel
if verify == @true
message("Last Step",'To complete this process please immediately logout of windows and choose "LOGIN USING DIAL-UP CONNECTION" option to synchronize your local and domain password')
goto goodbye
endif
quit = AskYesNo("Confirm Exit","Your PASSWORD will expire in %expire% day(s). Are you sure you want to exit?")
select quit
case 0
goto ChangePass
break
case 1
goto goodbye
break


:goodbye
exit

:LoadDialog
GetPasswordFormat=`WWWDLGED,6.1`
GetPasswordCaption=`Password Expiration Notice`
GetPasswordX=202
GetPasswordY=100
GetPasswordWidth=140
GetPasswordHeight=152
GetPasswordNumControls=009
GetPasswordProcedure=`DEFAULT`
GetPasswordFont=`DEFAULT`
GetPasswordTextColor=`DEFAULT`
GetPasswordBackground=`DEFAULT,DEFAULT`
GetPasswordConfig=0
GetPassword001=`015,123,044,012,PUSHBUTTON,DEFAULT,"Set Password",1,4,32,DEFAULT,DEFAULT,DEFAULT`
GetPassword002=`077,123,044,012,PUSHBUTTON,DEFAULT,"Cancel",0,5,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
GetPassword003=`065,049,056,012,EDITBOX,CurrentPass,DEFAULT,DEFAULT,1,16,DEFAULT,DEFAULT,DEFAULT`
GetPassword004=`065,079,056,012,EDITBOX,NewPass,DEFAULT,DEFAULT,2,16,DEFAULT,DEFAULT,DEFAULT`
GetPassword005=`005,007,131,032,VARYTEXT,Dialogtxt,DEFAULT,DEFAULT,6,0,"Microsoft Sans Serif|6144|40|34","0|0|0",DEFAULT`
GetPassword006=`065,097,056,012,EDITBOX,NewPassConfirm,DEFAULT,DEFAULT,3,16,DEFAULT,DEFAULT,DEFAULT`
GetPassword007=`015,049,046,012,STATICTEXT,DEFAULT,"Current Password:",DEFAULT,7,1024,DEFAULT,DEFAULT,DEFAULT`
GetPassword008=`015,079,046,012,STATICTEXT,DEFAULT,"New Password:",DEFAULT,8,1024,DEFAULT,DEFAULT,DEFAULT`
GetPassword009=`015,097,046,012,STATICTEXT,DEFAULT,"Confirm Password:",DEFAULT,9,1024,DEFAULT,DEFAULT,DEFAULT`
return

Article ID:   W16325
File Created: 2005:02:18:12:19:48
Last Updated: 2005:02:18:12:19:48