Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| -Username | [string] | Yes · Pipeline |
The identity to evaluate. Accepts SamAccountName, UPN, email address,
display name, DistinguishedName, or CanonicalName — same formats as
Get-MyUser.
|
| -SkipCredentialTest | [switch] | Optional | Suppresses the interactive password validation prompt entirely. Use when calling from scripts or automation where interactive prompts are not appropriate. |
| -PreferredSite | [string] | Optional | Prefer a DC in this AD site for the attribute query. Falls back to any available writable DC if the site has no DC for the user's domain. |
Check Severity Levels
Locked out
Account expired
Password expired
Smart card required
No logon in 90+ days
Password never expires
Must change at next logon
Logon hour restrictions
Cannot change password
Not locked out
Password healthy
Recent logon recorded
Output — PSCustomObject
Critical severity checks are present. When critical issues exist,
validation is automatically skipped to avoid misleading results — a locked or disabled
account will fail authentication regardless of password correctness.
logonHours array is
evaluated by checking for any byte not equal to 0xFF. All-0xFF
means unrestricted. Any restricted byte pattern sets severity to Restriction.
Examples
Test-MyUserStatus -Username "jdoe"
Resolves jdoe via Get-MyUser, runs all checks, prints the status report, then prompts to validate credentials if no critical issues are found.
Test-MyUserStatus -Username "john.doe@child1.corp.local" -SkipCredentialTest
Accepts UPN or email as identity. Skips the interactive prompt — suitable for scheduled tasks, runbooks, or pipeline use.
"jdoe","asmith","bwilson" | Test-MyUserStatus -SkipCredentialTest | Export-Csv C:\Reports\AccountStatus.csv -NoTypeInformation
Pipeline-friendly. The Checks array won't serialize cleanly to CSV — use HasCriticalIssues and the flat boolean fields for tabular reporting.
Test-MyUserStatus -Username "jdoe" -PreferredSite "Doha-Site" -SkipCredentialTest
Pins DC resolution to the Doha-Site AD site. Falls back to any writable DC in the user's domain if no DC exists in the preferred site.
Full Source
function Test-MyUserStatus { [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string]$Username, [Parameter(Mandatory = $false)] [switch]$SkipCredentialTest, [Parameter(Mandatory = $false)] [string]$PreferredSite ) process { # ── Step 1: Resolve identity and DC via Get-MyUser ──────────────────── Write-Verbose "Resolving identity '$Username' via Get-MyUser." $resolveParams = @{ Username = $Username } if ($PreferredSite) { $resolveParams['PreferredSite'] = $PreferredSite } try { $adUser = Get-MyUser @resolveParams -ErrorAction Stop } catch { Write-Error "Failed to resolve identity '$Username': $_" return } if (-not $adUser) { Write-Warning "User '$Username' not found in the forest." return } $samAccount = $adUser.SamAccountName $targetDC = $adUser.ResolvedDC # ── Step 2: Full attribute retrieval from the resolved DC ───────────── try { $raw = Get-ADUser -Identity $samAccount ` -Properties AccountExpirationDate, Enabled, LockedOut, pwdLastSet, 'msDS-UserPasswordExpiryTimeComputed', PasswordLastSet, PasswordExpired, PasswordNeverExpires, CannotChangePassword, userWorkstations, logonHours, LastLogonDate, SmartcardLogonRequired, UserAccountControl ` -Server $targetDC ` -ErrorAction Stop } catch { Write-Error "Failed to retrieve account attributes for '$samAccount' from '$targetDC': $_" return } # ── Step 3: Evaluate account conditions ─────────────────────────────── $checks = [System.Collections.Generic.List[PSCustomObject]]::new() $currentTime = Get-Date $hasCritical = $false function Add-Check { param([string]$Severity, [string]$Message) $checks.Add([PSCustomObject]@{ Severity = $Severity Message = $Message }) } # Account enabled if (-not $raw.Enabled) { Add-Check 'Critical' 'Account is disabled.' $hasCritical = $true } else { Add-Check 'OK' 'Account is enabled.' } # Lockout if ($raw.LockedOut) { Add-Check 'Critical' 'Account is locked out.' $hasCritical = $true } else { Add-Check 'OK' 'Account is not locked out.' } # Account expiration if ($raw.AccountExpirationDate -and $currentTime -gt $raw.AccountExpirationDate) { Add-Check 'Critical' "Account expired on $($raw.AccountExpirationDate.ToString('yyyy-MM-dd HH:mm'))." $hasCritical = $true } elseif ($raw.AccountExpirationDate -and $raw.AccountExpirationDate -gt $currentTime -and $raw.AccountExpirationDate -lt $currentTime.AddDays(14)) { Add-Check 'Warning' "Account expires soon: $($raw.AccountExpirationDate.ToString('yyyy-MM-dd HH:mm'))." } elseif (-not $raw.AccountExpirationDate) { Add-Check 'OK' 'Account has no expiration date.' } else { Add-Check 'OK' "Account expires: $($raw.AccountExpirationDate.ToString('yyyy-MM-dd HH:mm'))." } # Must change password at next logon if ($raw.pwdLastSet -eq 0) { Add-Check 'Warning' 'User must change password at next logon.' } # Password expiry if ($raw.PasswordNeverExpires) { Add-Check 'Warning' 'Password is set to never expire.' } elseif ($raw.pwdLastSet -ne 0) { $expiryRaw = $raw.'msDS-UserPasswordExpiryTimeComputed' if ($expiryRaw -ne [long]::MaxValue -and $expiryRaw -ne 0) { $passwordExpiry = [datetime]::FromFileTime($expiryRaw) if ($currentTime -ge $passwordExpiry) { Add-Check 'Critical' "Password expired on $($passwordExpiry.ToString('yyyy-MM-dd HH:mm'))." $hasCritical = $true } elseif ($passwordExpiry -lt $currentTime.AddDays(14)) { Add-Check 'Warning' "Password expires soon: $($passwordExpiry.ToString('yyyy-MM-dd HH:mm'))." } else { Add-Check 'OK' "Password expires: $($passwordExpiry.ToString('yyyy-MM-dd HH:mm'))." } } } # Smart card required if (($raw.UserAccountControl -band 0x40000) -eq 0x40000) { Add-Check 'Critical' 'Smart card is required for logon.' $hasCritical = $true } # Workstation restrictions if ($raw.userWorkstations) { Add-Check 'Restriction' "Logon restricted to workstations: $($raw.userWorkstations)." } # Logon hour restrictions (21-byte array; all 0xFF = unrestricted) if ($raw.logonHours) { $restricted = ($raw.logonHours | Where-Object { $_ -ne 0xFF }).Count -gt 0 if ($restricted) { Add-Check 'Restriction' 'Logon hours are restricted.' } } # Cannot change password if ($raw.CannotChangePassword) { Add-Check 'Restriction' 'User cannot change their own password.' } # Last logon recency if (-not $raw.LastLogonDate) { Add-Check 'Warning' 'No logon date recorded — account may never have logged on.' } elseif ($raw.LastLogonDate -lt $currentTime.AddDays(-90)) { Add-Check 'Warning' "No recent logon — last logon: $($raw.LastLogonDate.ToString('yyyy-MM-dd HH:mm'))." } else { Add-Check 'OK' "Last logon: $($raw.LastLogonDate.ToString('yyyy-MM-dd HH:mm'))." } # ── Step 4: Display status report ───────────────────────────────────── Write-Host '' Write-Host '────────────────────────────────────────────────────────' -ForegroundColor DarkCyan Write-Host ' Account Status Report' -ForegroundColor Cyan Write-Host '────────────────────────────────────────────────────────' -ForegroundColor DarkCyan Write-Host " User : $($adUser.DisplayName) ($samAccount)" -ForegroundColor Green Write-Host " UPN : $($adUser.UserPrincipalName)" -ForegroundColor Green Write-Host " Domain : $($adUser.Domain)" -ForegroundColor Green Write-Host " Resolved DC : $targetDC" -ForegroundColor DarkGray Write-Host '' foreach ($check in $checks) { switch ($check.Severity) { 'Critical' { Write-Host " [CRITICAL] $($check.Message)" -ForegroundColor Red } 'Warning' { Write-Host " [WARNING] $($check.Message)" -ForegroundColor Yellow } 'Restriction' { Write-Host " [RESTRICTION] $($check.Message)" -ForegroundColor Cyan } 'OK' { Write-Host " [OK] $($check.Message)" -ForegroundColor DarkGray } } } Write-Host '' if ($hasCritical) { Write-Host ' ✖ Critical issues detected.' -ForegroundColor Red } else { Write-Host ' ✔ No critical issues detected.' -ForegroundColor Green } Write-Host '────────────────────────────────────────────────────────' -ForegroundColor DarkCyan Write-Host '' # ── Step 5: Optional credential validation ──────────────────────────── $credentialResult = 'NotTested' if (-not $hasCritical -and -not $SkipCredentialTest) { $testCred = (Read-Host "Validate credentials for '$samAccount'? (y/n)").ToLower().Trim() if ($testCred -eq 'y') { $securePassword = Read-Host "Enter password for $samAccount" -AsSecureString Add-Type -AssemblyName System.DirectoryServices.AccountManagement $bstr = [IntPtr]::Zero $context = $null try { $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword) $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) $context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext( [System.DirectoryServices.AccountManagement.ContextType]::Domain, $adUser.Domain ) if ($context.ValidateCredentials($samAccount, $plainPassword)) { Write-Host ' [SUCCESS] Credentials are valid.' -ForegroundColor Green $credentialResult = 'Valid' } else { Write-Host ' [FAILURE] Authentication failed — incorrect password.' -ForegroundColor Red $credentialResult = 'Invalid' } } catch { Write-Error "Credential validation error: $_"; $credentialResult = 'Error' } finally { if ($context) { $context.Dispose() } if ($bstr -ne [IntPtr]::Zero) { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } Remove-Variable plainPassword -ErrorAction SilentlyContinue } } } # ── Step 6: Return structured output to pipeline ────────────────────── [PSCustomObject]@{ SamAccountName = $samAccount UserPrincipalName = $adUser.UserPrincipalName DisplayName = $adUser.DisplayName Domain = $adUser.Domain ResolvedDC = $targetDC Enabled = $raw.Enabled LockedOut = $raw.LockedOut AccountExpirationDate = $raw.AccountExpirationDate PasswordLastSet = $raw.PasswordLastSet PasswordNeverExpires = $raw.PasswordNeverExpires PasswordExpired = $raw.PasswordExpired MustChangePassword = ($raw.pwdLastSet -eq 0) SmartcardRequired = (($raw.UserAccountControl -band 0x40000) -eq 0x40000) WorkstationRestricted = [bool]$raw.userWorkstations LogonHoursRestricted = ($raw.logonHours -and ($raw.logonHours | Where-Object { $_ -ne 0xFF }).Count -gt 0) CannotChangePassword = $raw.CannotChangePassword LastLogonDate = $raw.LastLogonDate HasCriticalIssues = $hasCritical CredentialResult = $credentialResult Checks = $checks } } }