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


22 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