I have covered the creation of a "password reset" tools which enables users to reset their own passwords in Active Directory. The application was built with ASP.NET, running under a service account (pwdresetservice
), and should have been straightforward. However for accounts in a specific OU I kept hitting this frustrating error:
SetPassword failed for user TestUser42: Access is denied.
(Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
The user authentication was working fine, password complexity validation passed, but the actual password reset consistently failed with access denied.
Initial Approach: Standard Permission Assignment
My first instinct was to grant the service account the necessary permissions on the target OU. I created a PowerShell script to assign what I thought were the required permissions:
# Initial permissions script
Import-Module ActiveDirectory
$ServiceAccount = "BEAR\pwdresetservice"
$ISOU = "OU=Help Desk,OU=Corporate Users,OU=IT Operations,DC=bear,DC=local"
$acl = Get-Acl "AD:$ISOU"
# Reset Password permission
$accessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
[System.Security.Principal.NTAccount]$ServiceAccount,
[System.DirectoryServices.ActiveDirectoryRights]"ExtendedRight",
[System.Security.AccessControl.AccessControlType]"Allow",
[System.DirectoryServices.ActiveDirectorySecurityInheritance]"Descendents",
[System.Guid]"00299570-246d-11d0-a768-00aa006e0529"
)
$acl.SetAccessRule($accessRule)
Set-Acl "AD:$ISOU" $acl
After running this script, I verified the permissions were applied correctly, but the access denied error persisted.
Diagnostic Deep Dive
The generic "access denied" error wasn't giving me enough information. I modified the web application's exception handling to capture the inner exception details:
try
{
userEntry.Invoke("SetPassword", new object[] { newPassword });
userEntry.CommitChanges();
WriteToLog(string.Format("Password reset successful for user: {0}", username));
return true;
}
catch (System.Reflection.TargetInvocationException ex)
{
// Log the inner exception which contains the real error
string innerMessage = ex.InnerException != null ? ex.InnerException.Message :
ex.Message;
WriteToLog(string.Format("SetPassword failed for user {0}: {1}", username,
innerMessage));
if (innerMessage.Contains("access is denied") ||
innerMessage.Contains("insufficient rights"))
{
WriteToLog(string.Format("Access denied setting password for user {0}: {1}",
username, innerMessage));
throw new Exception("Access denied - insufficient permissions: " + innerMessage);
}
}
This gave me the actual HRESULT error (0x80070005), confirming it was indeed a permissions issue, not a password policy violation.
Permission Verification Script
I created a diagnostic script to verify what permissions the service account actually had:
# Check what permissions pwdresetservice actually has
$ServiceAccount = "BEAR\pwdresetservice"
$ISOU = "OU=Corporate Users,OU=IT Operations,DC=bear,DC=local"
$acl = Get-Acl "AD:$ISOU"
$serviceAccountPermissions = $acl.Access | Where-Object { $_.IdentityReference -eq
$ServiceAccount }
if ($serviceAccountPermissions) {
Write-Host "Found permissions for $ServiceAccount:" -ForegroundColor Green
foreach ($permission in $serviceAccountPermissions) {
Write-Host " Rights: $($permission.ActiveDirectoryRights)" -ForegroundColor White
Write-Host " Type: $($permission.AccessControlType)" -ForegroundColor White
Write-Host " Object Type: $($permission.ObjectType)" -ForegroundColor White
Write-Host " ---" -ForegroundColor Gray
}
}
The permissions were there, but something was still blocking the password reset.
The Missing Piece: Change Password Extended Right
I discovered that the ADSI SetPassword
method requires both the Reset Password and Change Password extended rights. I was missing the Change Password permission:
# Add the missing Change Password permission
$accessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
[System.Security.Principal.NTAccount]$ServiceAccount,
[System.DirectoryServices.ActiveDirectoryRights]"ExtendedRight",
[System.Security.AccessControl.AccessControlType]"Allow",
[System.DirectoryServices.ActiveDirectorySecurityInheritance]"Descendents",
[System.Guid]"ab721a53-1e2f-11d0-9819-00aa0040529b" # Change Password GUID
)
Using dsacls
proved more reliable than PowerShell for setting these permissions:
dsacls "OU=Help Desk,OU=Corporate Users,OU=IT Operations,DC=bear,DC=local" /G
"BEAR\pwdresetservice:CA;Reset Password"
dsacls "OU=Help Desk,OU=Corporate Users,OU=IT Operations,DC=bear,DC=local" /G
"BEAR\pwdresetservice:CA;Change Password"
dsacls "OU=Help Desk,OU=Corporate Users,OU=IT Operations,DC=bear,DC=local" /G
"BEAR\pwdresetservice:WP"
But still... access denied - obviously, this was something else.
The Breakthrough: adminCount=1 Discovery
After exhausting permission-based solutions, I created a comprehensive diagnostic script to analyze the target user account:
# Comprehensive user analysis
$TestUser = "TestUser42"
$user = Get-ADUser -Identity $TestUser -Properties DistinguishedName, MemberOf, adminCount
Write-Host "User: $($user.DistinguishedName)"
Write-Host "adminCount: $($user.adminCount)"
if ($user.MemberOf) {
Write-Host "Group memberships:"
$user.MemberOf | ForEach-Object {
$group = Get-ADGroup -Identity $_
Write-Host " - $($group.Name)"
# Check for protected groups
if ($group.Name -like "*admin*" -or $group.Name -eq "Domain Admins") {
Write-Host " ⚠️ WARNING: This is a protected/admin group!"
-ForegroundColor Red
}
}
}
The output revealed the smoking gun:
User: CN=Test User,OU=Help Desk,OU=Corporate Users,OU=IT Operations,DC=bear,DC=local
adminCount: 1
Group memberships:
- PasswordPolicy-Elevated
⚠️ WARNING: This is a protected/admin group!
- Twingate-Administrators
⚠️ WARNING: This is a protected/admin group!
- LocalWorkstation-Administrator
⚠️ WARNING: This is a protected/admin group!
- GRP-ServiceDesk-Techical
⚠️ WARNING: This is a protected/admin group!
[... multiple admin groups ...]
Understanding adminCount=1
The adminCount=1
attribute is automatically set by Active Directory when a user account is added to certain protected groups (Domain Admins, Enterprise Admins, Schema Admins, etc.). This attribute has significant security implications:
- Inheritance is disabled - The account no longer inherits permissions from parent OUs
- SDProp process runs hourly to reset the account's ACL to default protected settings
- Standard delegation doesn't work - Normal OU-level permissions don't apply
This explained why my carefully crafted permissions on the OU had no effect on this particular user account.
Testing the Theory
I created a test to confirm this was the issue:
# Test if PowerShell password reset works (uses different auth path)
try {
$newPassword = "TempPassword123!" | ConvertTo-SecureString -AsPlainText -Force
Set-ADAccountPassword -Identity $TestUser -NewPassword $newPassword -Reset
Write-Host "✓ PowerShell password reset SUCCESSFUL!"
} catch {
Write-Host "✗ PowerShell password reset FAILED: $($_.Exception.Message)"
}
PowerShell succeeded, confirming that the service account had the necessary rights, but ADSI was being blocked by the protected account status.
The Solution
If you have Users that have accounts that need to be in protected users groups then obviously you can’t remove the users because that would also remove their access,
If you are a adopting “least administrative access” and your permissions are only allocated when it’s required, that would simply mean that the user would be unable to reset their password while they are in the protected groups, this is because “SDProp” Will run on regular intervals and simply add the admin count attribute back on the account.
This means there is only one solution when you’re looking to reset passwords, with some custom website running into a service account.
Account Operators Group
Aadding the service account to the Account Operators built-in group:
Add-ADGroupMember -Identity "Account Operators" -Members "pwdresetservice"
Account Operators has inherent rights to manage user accounts, including password resets for protected accounts, bypassing normal ACL restrictions.
The Resolution
After adding the service account to Account Operators and waiting for replication, the password reset functionality worked perfectly. The access denied errors disappeared, and users could successfully reset their passwords through the web application.
This debugging journey reinforced an important principle: in Active Directory, not all user accounts are created equal. Understanding the security implications of protected accounts and the adminCount
attribute is crucial for anyone implementing AD management solutions.