Managing Active Directory (AD) security can be challenging, particularly when identifying accounts sharing passwords or using weak authentication practices. Enter LazyAdminFinder, a powerful, lightweight PowerShell tool that quickly reveals shared password usage and improves your organization's AD security posture.

What is LazyAdminFinder?

LazyAdminFinder is a PowerShell script leveraging the DSInternals module to:

Designed specifically for Active Directory administrators, LazyAdminFinder provides quick, actionable insights into password security.

Why Use LazyAdminFinder?

How Does LazyAdminFinder Work?

Fetch and Filter User Accounts

The script collects AD user accounts, filtering out disabled, machine, and service accounts to focus only on active users.

NTLM Hash Extraction and Archiving

LazyAdminFinder extracts NTLM hashes from accounts and securely archives their SHA-256 hashes for historical comparison.

Password Reuse Detection

By comparing the current run to previous archives, the tool detects password reuse, flagging accounts with potential security risks. This helps identify:

Shared Password Grouping

The script identifies and groups accounts sharing identical NTLM hashes, which can indicate either intentional password sharing or multiple users independently choosing the same password—often due to weak password habits. For example, if several users select a common password like "Summer2025!", LazyAdminFinder will group these accounts together, allowing you to quickly spot both potential shared passwords and risky parallel thinking in password creation.

Consider running LazyAdminFinder regularly based on your organization's password reset policy. For instance, if your policy mandates a password reset every 90 days, schedule the script to run approximately every 100 days. This timing ensures you capture instances where admins or privileged users might reset their passwords back to previously used ones. If your organization doesn't enforce a password reuse policy, running LazyAdminFinder periodically becomes even more crucial to identify and mitigate potential security risks.

Installing LazyAdminFinder

Step 1: Preparation

Install-Module DSInternals

Step 2: Configure the Script

Customize these essential parameters within the provided script:

$dc = "your-domain-controller.example.com"
$nc = "DC=yourdomain,DC=com"
$outputReport = "C:\Path\To\SharedPasswordsReport.csv"
$archiveDir = "C:\Path\To\HashArchives"

Step 3: Run the Script

Execute the script as a Domain admin:

powershell.exe -ExecutionPolicy Bypass -File "C:\Path\To\LazyAdminFinder.ps1"

Reviewing the Results

Use Cases for LazyAdminFinder

Customizing LazyAdminFinder

Feel free to adjust the script according to your environment and needs:

Benefits of Using LazyAdminFinder


LazyAdminFinder simplifies the critical task of password auditing in Active Directory, empowering administrators to maintain a secure, compliant, and efficient IT environment.

Discover shared passwords, remediate security risks, and fortify your AD security effortlessly with LazyAdminFinder.

#Another        /\_[]_/\
#    fine      |] _||_ [|
#       ___     \/ || \/
#      /___\       ||
#     (|0 0|)      ||
#   __/{\U/}\_ ___/vvv
#  / \  {~}   / _|_P|
#  | /\  ~   /_/   []
#  |_| (____)        
#  \_]/______\  Barberion  
#     _\_||_/_     Production      
#    (_,_||_,_)
#
Import-Module DSInternals

# === Parameters ===
$dc           = "your-domain-controller.example.com"
$nc           = "DC=yourdomain,DC=com"
$outputReport = "C:\Path\To\SharedPasswordsReport.csv"
$archiveDir   = "C:\Path\To\HashArchives"

# Ensure archive directory exists
if (-not (Test-Path $archiveDir)) {
    New-Item -ItemType Directory -Path $archiveDir | Out-Null
}

$timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")

# === 1. Fetch & filter AD user accounts ===
Write-Host "[+] Fetching AD user accounts (skipping machines, service & disabled)..."
$filteredAccounts = Get-ADReplAccount -All -Server $dc -NamingContext $nc |
    Where-Object { $_.NTHash -and $_.SamAccountName } |
    Where-Object { $_.SamAccountName -notmatch '\$$' } |
    Where-Object { $_.SamAccountName -notmatch '^(?i)(svc|service)' } |
    Where-Object {
        if ($_.PSObject.Properties.Match('IsDisabled').Count -gt 0) {
            -not $_.IsDisabled
        }
        else {
            # bit 2 = disabled
            ( ($_.UserAccountControl -band 2) -eq 0 )
        }
    }

if (-not $filteredAccounts) {
    Write-Host "[-] No matching user accounts found. Exiting."
    exit
}

# === 2. Convert NTLM byte array to hex string ===
$filteredAccounts | ForEach-Object {
    $hex = [BitConverter]::ToString($_.NTHash) -replace '-', ''
    $_ | Add-Member -MemberType NoteProperty -Name NTLMHashString -Value $hex -Force
}

# === 3. Archive SHA-256 of each NTLM hash ===
Write-Host "[+] Archiving SHA-256 of each NTLM hash..."
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$currentArchive = $filteredAccounts | ForEach-Object {
    $shaBytes = $sha256.ComputeHash($_.NTHash)
    $shaHex   = [BitConverter]::ToString($shaBytes) -replace '-', ''
    [PSCustomObject]@{
        SamAccountName = $_.SamAccountName
        HashSha256     = $shaHex
    }
}
$sha256.Dispose()

$archiveFile = Join-Path $archiveDir "UserHashArchive_$timestamp.csv"
$currentArchive | Export-Csv -Path $archiveFile -NoTypeInformation

# === 4. Compare to previous archive ===
$allArchives = Get-ChildItem -Path $archiveDir -Filter "UserHashArchive_*.csv" |
               Sort-Object LastWriteTime -Descending

if ($allArchives.Count -gt 1) {
    $previousFile = $allArchives[1].FullName
    Write-Host "[+] Comparing this run to previous archive: $previousFile"
    $prevData = Import-Csv $previousFile

    $reused = Compare-Object `
        -ReferenceObject $prevData `
        -DifferenceObject $currentArchive `
        -Property SamAccountName, HashSha256 `
        -IncludeEqual |
      Where-Object { $_.SideIndicator -eq '==' } |
      Select-Object SamAccountName, HashSha256

    if ($reused) {
        $reuseReport = Join-Path $archiveDir "PasswordReuse_$timestamp.csv"
        Write-Host "[!] $($reused.Count) accounts reused their old hash; exporting to $reuseReport"
        $reused | Export-Csv -Path $reuseReport -NoTypeInformation
    }
    else {
        Write-Host "[+] No password‑reuse detected this run."
    }
}
else {
    Write-Host "[+] Only one archive present; skipping reuse comparison."
}

# === 5. Shared‑password grouping & report ===
Write-Host "[+] Grouping accounts by shared NTLM hash..."
$sharedGroups = $filteredAccounts |
    Group-Object -Property NTLMHashString |
    Where-Object { $_.Count -gt 1 }

if (-not $sharedGroups) {
    Write-Host "[-] No shared hashes found."
    exit
}

$reportResults = @()
Write-Host "[+] Generating shared‑password report:"
foreach ($g in $sharedGroups) {
    $h     = $g.Name
    $users = $g.Group | ForEach-Object { $_.SamAccountName }
    $reportResults += [PSCustomObject]@{
        SharedHash = $h
        UserCount  = $users.Count
        Users      = $users -join ', '
    }

    Write-Host '----------------------------------------------'
    Write-Host "Shared Hash: $h"
    Write-Host "User Count : $($users.Count)"
    Write-Host "Users      : $($users -join ', ')"
    Write-Host '----------------------------------------------`n'
}

Write-Host "[+] Saving shared‑password report to $outputReport"
$reportResults | Export-Csv -Path $outputReport -NoTypeInformation

Write-Host "[Summary] Shared groups: $($reportResults.Count); Total archives: $($allArchives.Count)"