PowerShell: Verify AD Credentials and Detect Password Reset Requirement While Checking Last Password Validity


2 views

When working with Active Directory authentication in PowerShell, we often need more than just basic credential verification. The scenario requires:

  • Validating current credentials
  • Identifying if password reset is required
  • Confirming the credentials match the user's last known password

Here's a more robust solution that addresses all requirements:

function Test-ADAuthenticationExtended {
    param(
        [string]$username,
        [string]$password,
        [switch]$CheckLastPassword
    )
    
    # Basic authentication check
    $entry = New-Object DirectoryServices.DirectoryEntry("LDAP://$($env:USERDNSDOMAIN)", $username, $password)
    
    if ($entry.psbase.Name -eq $null) {
        return [PSCustomObject]@{
            Authenticated = $false
            PasswordResetRequired = $false
            LastPasswordValid = $false
        }
    }
    
    # Check password reset requirement
    $searcher = [DirectoryServices.DirectorySearcher]::new($entry)
    $searcher.Filter = "(sAMAccountName=$username)"
    $result = $searcher.FindOne()
    
    $pwdLastSet = [datetime]::FromFileTime($result.Properties["pwdlastset"][0])
    $maxPwdAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
    
    $resetRequired = $false
    if ($maxPwdAge -and $pwdLastSet -lt (Get-Date).AddDays(-$maxPwdAge.TotalDays)) {
        $resetRequired = $true
    }
    
    # Check last password validity if requested
    $lastPwdValid = $false
    if ($CheckLastPassword) {
        try {
            $lastPwdEntry = New-Object DirectoryServices.DirectoryEntry("LDAP://$($env:USERDNSDOMAIN)", $username, $password)
            $lastPwdValid = ($lastPwdEntry.psbase.Name -ne $null)
        } catch {
            $lastPwdValid = $false
        }
    }
    
    return [PSCustomObject]@{
        Authenticated = $true
        PasswordResetRequired = $resetRequired
        LastPasswordValid = $lastPwdValid
    }
}

Here's how to use this enhanced function:

# Basic usage
$result = Test-ADAuthenticationExtended -username "jdoe" -password "P@ssw0rd!"
$result | Format-List

# With last password check
$result = Test-ADAuthenticationExtended -username "jdoe" -password "OldP@ssw0rd!" -CheckLastPassword
$result | Format-List

For enterprise scenarios, consider these additional checks:

function Get-ADPasswordStatus {
    param($username)
    
    $user = Get-ADUser $username -Properties PasswordLastSet, PasswordExpired, PasswordNeverExpires
    
    return [PSCustomObject]@{
        PasswordLastSet = $user.PasswordLastSet
        PasswordExpired = $user.PasswordExpired
        PasswordNeverExpires = $user.PasswordNeverExpires
        DaysUntilExpiration = if ($user.PasswordNeverExpires) {
            $null
        } else {
            $pwdPolicy = Get-ADDefaultDomainPasswordPolicy
            $expirationDate = $user.PasswordLastSet + $pwdPolicy.MaxPasswordAge
            ($expirationDate - (Get-Date)).Days
        }
    }
}
try {
    $result = Test-ADAuthenticationExtended -username "nonexistent" -password "wrong"
    
    if (-not $result.Authenticated) {
        Write-Warning "Authentication failed"
    } elseif ($result.PasswordResetRequired) {
        Write-Warning "Password reset required"
    }
} catch [System.Runtime.InteropServices.COMException] {
    Write-Error "Active Directory communication error: $_"
} catch {
    Write-Error "Unexpected error: $_"
}

When managing Active Directory environments, administrators often need to verify user credentials while simultaneously checking password status. The basic authentication function works well for simple validation:

Function Test-ADAuthentication {
    param($username,$password)
    (new-object directoryservices.directoryentry "",$username,$password).psbase.name -ne $null
}

To properly handle both current authentication and password reset scenarios, we need a more comprehensive solution:

Function Test-ADCredentialStatus {
    param(
        [Parameter(Mandatory=$true)]
        [string]$username,
        
        [Parameter(Mandatory=$true)]
        [string]$password,
        
        [string]$domain = $env:USERDOMAIN
    )
    
    $currentAuth = $false
    $passwordExpired = $false
    $lastPasswordValid = $false
    
    try {
        # Test current credentials
        $de = New-Object DirectoryServices.DirectoryEntry "LDAP://$domain",$username,$password
        if($de.psbase.Name -ne $null) {
            $currentAuth = $true
            
            # Check if password needs reset
            $searcher = New-Object DirectoryServices.DirectorySearcher $de
            $searcher.Filter = "(samAccountName=$username)"
            $result = $searcher.FindOne()
            
            if($result.Properties.pwdLastSet[0] -eq 0) {
                $passwordExpired = $true
            }
        }
    }
    catch [System.Runtime.InteropServices.COMException] {
        # If error is invalid credentials, test with previous password
        if($_.Exception.ErrorCode -eq -2147023570) {
            $lastPasswordValid = Test-LastPassword $username $password $domain
        }
    }
    
    [PSCustomObject]@{
        Username = $username
        CurrentCredentialsValid = $currentAuth
        PasswordExpired = $passwordExpired
        LastPasswordValid = $lastPasswordValid
    }
}

Function Test-LastPassword {
    param($username, $password, $domain)
    
    # This would require additional AD permissions and configuration
    # Implementation depends on your AD auditing setup
    # Placeholder for actual last password verification logic
    return $false
}

When implementing this solution, consider these important aspects:

# Example usage:
$credCheck = Test-ADCredentialStatus -username "jdoe" -password "P@ssw0rd123"
Write-Output $credCheck

# Expected output format:
# Username      : jdoe
# CurrentCredentialsValid : True
# PasswordExpired         : False
# LastPasswordValid       : False

The script should handle these common cases:

  • Valid current credentials with non-expired password
  • Valid current credentials with expired password
  • Invalid current credentials but valid previous password
  • Completely invalid credentials

To implement this solution completely, you'll need:

  • Read access to user objects in AD
  • Password auditing enabled in your domain
  • Proper error handling for permission-related issues