userAccountControl
is a bitmask, so you should be using the or
operator to combine flags, not the +
operator.
But, more importantly, according to How to use the UserAccountControl flags to manipulate user account properties:
PASSWD_CANT_CHANGE
Note: You cannot assign this permission by directly modifying the UserAccountControl attribute. For information about how to set the permission programmatically, see the "Property flag descriptions" section.
Where the "Property flag descriptions" section says:
That page, in turn, says:
The ability of a user to change their own password is a permission that can be grant or denied. To deny this permission, set two ACEs in the security descriptor discretionary access control list (DACL) of the user object with the ADS_ACETYPE_ACCESS_DENIED_OBJECT ace type. One ACE denies the permission to the user and another ACE denies the permission to the Everyone group. Both ACEs are object-specific deny ACEs that specify the GUID of the extended permission for changing passwords. To grant this permission, set the same ACEs with the ADS_ACETYPE_ACCESS_ALLOWED_OBJECT ace type.
The following procedure describes how to modify or add ACEs for this permission.
To modify or add the ACEs for this permission
Bind to the user object.
Obtain the IADsSecurityDescriptor
object from the ntSecurityDescriptor
property of the user object.
Obtain an IADsAccessControlList
interface for the security descriptor from the IADsSecurityDescriptor.DiscretionaryAcl
property.
Enumerate the ACEs for the object and search for the ACEs that have the change password GUID ({AB721A53-1E2F-11D0-9819-00AA0040529B}
) for the IADsAccessControlEntry.ObjectType
property and "Everyone" or "NT AUTHORITY\SELF" for the IADsAccessControlEntry.Trustee
property.
Note: The "Everyone" and "NT AUTHORITY\SELF" strings are localized based on the language of the first domain controller in the domain. Because of this, the strings should not be used directly. The account names should be obtained at run time by calling the LookupAccountSid
function with the SID for "Everyone" ("S-1-1-0") and "NT AUTHORITY\SELF" ("S-1-5-10") well-known security principals. The GetSidAccountName
, GetSidAccountName_Everyone
, and GetSidAccountName_Self
C++ example functions shown in Reading User Cannot Change Password (LDAP Provider) demonstrate how to do this.
Modify the IADsAccessControlEntry.AceType
property of the ACEs that were found to ADS_ACETYPE_ACCESS_DENIED_OBJECT
if the user cannot change their password or ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
if the user can change their password.
If the "Everyone" ACE is not found, create a new IADsAccessControlEntry
object that contains the property values shown in the table below and add the new entry to the ACL with the IADsAccessControlList.AddAce
method.
If the "NT AUTHORITY\SELF" ACE is not found, create a new IADsAccessControlEntry
object with the same property values shown in the table below except the Trustee property contains the account name for SID "S-1-5-10" ("NT AUTHORITY\SELF"). Add the entry to the ACL with the IADsAccessControlList.AddAce
method.
To update the ntSecurityDescriptor
property of the object, call the IADs.Put
method with the same IADsSecurityDescriptor
obtained in Step 2.
Commit the local changes to the server with the IADs.SetInfo
method.
If either of the ACEs were created, you must reorder the ACL so that the ACEs are in the correct order. To do this, call the GetNamedSecurityInfo
function with the LDAP ADsPath of the object and then the SetNamedSecurityInfo
function with the same DACL. This reordering will occur automatically when the ACEs are added.
The following table lists the IADsAccessControlEntry
object property values.
AccessMask
ADS_RIGHT_DS_CONTROL_ACCESS
AceType
ADS_ACETYPE_ACCESS_DENIED_OBJECT
if the user cannot change their password or ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
if the user can change their password.
AceFlags
0
Flags
ADS_FLAG_OBJECT_TYPE_PRESENT
ObjectType
"{AB721A53-1E2F-11D0-9819-00AA0040529B}" which is the change password GUID in string form.
InheritedObjectType
Not used
Trustee
Account name for SID "S-1-1-0" (Everyone).
There is a fairly lengthy code example provided on the same page.