From 906bb52638faddd1e2e3f77ae60f69db1ef06f89 Mon Sep 17 00:00:00 2001 From: Tomas Kracmar Date: Tue, 9 Jun 2026 16:23:38 +0200 Subject: [PATCH] fix(Test-WeakADPasswords): add comprehensive DCSync diagnostic dump When Get-ADReplAccount or Test-PasswordQuality throws, the catch block now dumps the full exception chain (type, message, HResult, source, target site, stack trace, inner exceptions) along with runtime context (Elysium version, PS version, DSInternals version, DC, domain, account). Output goes to console and a timestamped diagnostic file under Reports/ for offline analysis. --- Test-WeakADPasswords.ps1 | 58 +++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/Test-WeakADPasswords.ps1 b/Test-WeakADPasswords.ps1 index 6a25844..b298f52 100644 --- a/Test-WeakADPasswords.ps1 +++ b/Test-WeakADPasswords.ps1 @@ -631,16 +631,62 @@ function Test-WeakADPasswords { $testResults = $accounts | Test-PasswordQuality -WeakPasswordHashesSortedFile $resolvedHashFile.Path Write-Verbose "Password quality test completed." } catch { - $message = $_.Exception.Message + $ex = $_.Exception + $diagLines = [System.Collections.Generic.List[string]]::new() + $diagLines.Add('========================================') + $diagLines.Add('ELYSLUM DCSYNC DIAGNOSTIC DUMP') + $diagLines.Add('========================================') + $diagLines.Add("Timestamp : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") + $diagLines.Add("Script Ver : $ElysiumVersion") + $diagLines.Add("PS Version : $($PSVersionTable.PSVersion)") + $diagLines.Add("PS Edition : $($PSVersionTable.PSEdition)") + $diagLines.Add("DSInternals : $((Get-Module -Name DSInternals).Version)") + $diagLines.Add("DC : $($selectedDomain['DC'])") + $diagLines.Add("Domain : $($selectedDomain.Name)") + $diagLines.Add("Account : $($credential.UserName)") + $diagLines.Add("DomainDN : $($domainInfo.DistinguishedName)") + $diagLines.Add('') + $diagLines.Add('--- EXCEPTION CHAIN ---') + $depth = 0 + $currentEx = $ex + while ($null -ne $currentEx) { + $diagLines.Add("Exception $depth : $($currentEx.GetType().FullName)") + $diagLines.Add(" Message : $($currentEx.Message)") + $diagLines.Add(" HResult : 0x$($currentEx.HResult.ToString('X8'))") + $diagLines.Add(" Source : $($currentEx.Source)") + if ($currentEx.TargetSite) { + $diagLines.Add(" TargetSite : $($currentEx.TargetSite)") + } + if ($currentEx.StackTrace) { + $diagLines.Add(" StackTrace :`n$($currentEx.StackTrace -replace '^', ' ')") + } + $diagLines.Add('') + $currentEx = $currentEx.InnerException + $depth++ + } + $diagLines.Add('--- END DIAGNOSTIC DUMP ---') + + $diagText = $diagLines -join "`r`n" + Write-Host $diagText -ForegroundColor Red + + $diagPath = Join-Path -Path $reportPathBase -ChildPath "dcsync-diag-$timestamp.txt" + try { + New-Item -ItemType Directory -Path $reportPathBase -Force | Out-Null + [System.IO.File]::WriteAllText($diagPath, $diagText, [System.Text.Encoding]::UTF8) + Write-Host ("Diagnostic dump written to: {0}" -f $diagPath) + } catch { + Write-Warning ("Could not write diagnostic dump to disk: {0}" -f $_.Exception.Message) + } + + # Still emit the concise error for the operator + $message = $ex.Message if ($message -match 'Access is denied') { Write-Error ("Access denied while reading replication data from '{0}' using '{1}'. Ensure this account has Replicating Directory Changes, Replicating Directory Changes All, and Replicating Directory Changes In Filtered Set on the domain." -f $selectedDomain["DC"], $credential.UserName) - return - } - if ($message -match 'rejected the client credentials|unknown user name|bad password|logon failure') { + } elseif ($message -match 'rejected the client credentials|unknown user name|bad password|logon failure') { Write-Error ("Credentials for '{0}' were rejected by '{1}'. Re-run and provide valid domain credentials." -f $credential.UserName, $selectedDomain["DC"]) - return + } else { + Write-Error ("An error occurred while testing passwords: {0}" -f $message) } - Write-Error ("An error occurred while testing passwords: {0}" -f $message) return } finally { if ($resolvedHashFile -and $resolvedHashFile.IsTemporary -and (Test-Path -LiteralPath $resolvedHashFile.Path)) {