3333 Domain Controller / ADWS-capable server to use for all AD queries
3434 (e.g. dc01.foo.bar).
3535
36+ . OUTPUTS
37+ - MSADPT_DC_Details_<DCName>_<timestamp>.csv
38+ Detailed Domain Controller information.
39+
40+ - MSADPT_DC_Kerberoastable_<DCName>_<timestamp>.csv
41+ User accounts with Service Principal Names.
42+ Consumed by MSADPT_exploit_privesc_initial.ps1.
43+ Required downstream columns:
44+ - SamAccountName
45+ - ServicePrincipalName
46+
47+ - MSADPT_DC_ASREPRoastable_<DCName>_<timestamp>.csv
48+ User accounts with Kerberos pre-authentication disabled.
49+ Consumed by MSADPT_exploit_privesc_initial.ps1.
50+ Required downstream columns:
51+ - SamAccountName
52+ - UserPrincipalName
53+
54+ - MSADPT_DC_LAPSInfo_<DCName>_<timestamp>.csv
55+ LAPS-related computer records and reader group information.
56+ Consumed by MSADPT_exploit_privesc_initial.ps1.
57+ Required downstream columns:
58+ - DCName
59+ - LAPS_ComputerDetails
60+
3661. EXAMPLE
3762 .\MSADPT_enumerate_dc2.ps1 `
3863 -InputDcCsvPath "C:\temp\MSADPT_Output\MSADPT_DCs.csv" `
4570param (
4671 [Parameter (Mandatory )]
4772 [ValidateScript ({
48- if (-not (Test-Path $_ )) {
73+ if (-not (Test-Path - LiteralPath $_ - PathType Leaf )) {
4974 throw " Input file '$_ ' does not exist."
5075 }
5176 $true
5277 })]
5378 [string ]$InputDcCsvPath ,
5479
5580 [Parameter (Mandatory )]
81+ [ValidateNotNullOrEmpty ()]
5682 [string ]$OutputBaseDir ,
5783
5884 [Parameter (Mandatory )]
85+ [ValidateNotNull ()]
5986 [PSCredential ]$Credential ,
6087
6188 [Parameter (Mandatory )]
89+ [ValidateNotNullOrEmpty ()]
6290 [string ]$DomainFQDN ,
6391
6492 [Parameter (Mandatory )]
93+ [ValidateNotNullOrEmpty ()]
6594 [string ]$AdServer
6695)
6796
@@ -70,28 +99,24 @@ param(
7099# ---------------------------------------------------------------------
71100$helpersModulePath = Join-Path $PSScriptRoot ' MSADPT.Helpers.psm1'
72101
73- if (-not (Test-Path - LiteralPath $helpersModulePath )) {
102+ if (-not (Test-Path - LiteralPath $helpersModulePath - PathType Leaf )) {
74103 Write-Error " Required helper module not found at '$helpersModulePath '. Aborting."
75104 exit 1
76105}
77106
78107Import-Module $helpersModulePath - Force - ErrorAction Stop
79108
80- Write-MSADPTLog - Message " MSADPT_enumerate_dc.ps1 starting." - Level ' INFO'
109+ # ---------------------------------------------------------------------
110+ # Runtime state
111+ # ---------------------------------------------------------------------
112+ $ScriptStartTime = Get-Date - Format " yyyyMMdd"
113+
114+ Write-MSADPTLog - Message " MSADPT_enumerate_dc2.ps1 starting." - Level ' INFO'
81115Write-MSADPTLog - Message " DomainFQDN : $DomainFQDN "
82116Write-MSADPTLog - Message " AdServer : $AdServer "
83117Write-MSADPTLog - Message " Input CSV : $InputDcCsvPath "
84118Write-MSADPTLog - Message " Output Dir : $OutputBaseDir "
85119
86- # ---------------------------------------------------------------------
87- # Pre-flight: AD connectivity check
88- # ---------------------------------------------------------------------
89- $rootDSE = Test-MSADPTADConnectivity - Credential $Credential - AdServer $AdServer
90- if (-not $rootDSE ) {
91- Write-MSADPTLog - Message " Active Directory connectivity pre-flight failed. Aborting." - Level ' ERROR'
92- exit 1
93- }
94-
95120# ---------------------------------------------------------------------
96121# Ensure output directory exists
97122# ---------------------------------------------------------------------
@@ -108,6 +133,39 @@ if (-not $DCs) {
108133 exit 0
109134}
110135
136+ $requiredColumns = @ (' Name' , ' IPv4Address' )
137+ $actualColumns = @ ($DCs [0 ].PSObject.Properties.Name)
138+
139+ foreach ($column in $requiredColumns ) {
140+ if ($actualColumns -notcontains $column ) {
141+ Write-MSADPTLog - Message " Input CSV is missing required column '$column '. Aborting." - Level ' ERROR'
142+ exit 1
143+ }
144+ }
145+
146+
147+ $RequiredModules = @ (' ActiveDirectory' )
148+
149+ foreach ($module in $RequiredModules ) {
150+ if (-not (Get-Module - ListAvailable - Name $module )) {
151+ Write-MSADPTLog - Message " Required module '$module ' is not available. Aborting." - Level ' ERROR'
152+ exit 1
153+ }
154+
155+ if (-not (Get-Module - Name $module )) {
156+ Import-Module $module - ErrorAction Stop
157+ Write-MSADPTLog - Message " Imported module '$module '." - Level ' INFO'
158+ }
159+ }
160+
161+ $adSplat = New-MSADPTAdCommandSplat - Server $AdServer - Credential $Credential
162+
163+ $rootDSE = Test-MSADPTADConnectivity - Credential $Credential - AdServer $AdServer
164+ if (-not $rootDSE ) {
165+ Write-MSADPTLog - Message " Active Directory connectivity pre-flight failed. Aborting." - Level ' ERROR'
166+ exit 1
167+ }
168+
111169# ---------------------------------------------------------------------
112170# Iterate each Domain Controller
113171# ---------------------------------------------------------------------
@@ -163,7 +221,7 @@ foreach ($DC in $DCs) {
163221
164222 $DCInfo |
165223 Select-Object Name, HostName, IPv4Address, OperatingSystem, Site, IsGlobalCatalog, IsReadOnly |
166- Export-Csv - Path (Join-Path $CurrentDCOutputDir " MSADPT_DC_Details_$DCName .csv" ) - NoTypeInformation - Force
224+ Export-Csv - Path (Join-Path $CurrentDCOutputDir " MSADPT_DC_Details_${ DCName} _ $ScriptStartTime .csv" ) - NoTypeInformation - Force
167225
168226 Write-MSADPTLog - Message " DC details saved for $DCName ."
169227 }
@@ -172,6 +230,206 @@ foreach ($DC in $DCs) {
172230 $DCErrors += " DC info failed"
173231 }
174232 }
233+ # -----------------------------------------------------------------
234+ # 3. Kerberoastable Account Discovery
235+ # -----------------------------------------------------------------
236+ if (Prompt- User - PromptText " Identify Kerberoastable accounts (user accounts with SPNs) from ${DCName} ?" ) {
237+ Write-MSADPTLog - Message " Identifying Kerberoastable accounts using explicit AD server '$AdServer '." - Level ' INFO'
238+
239+ try {
240+ $KerberoastableAccounts = @ (
241+ Get-ADUser @adSplat `
242+ - LDAPFilter ' (&(objectCategory=person)(objectClass=user)(servicePrincipalName=*))' `
243+ - Properties ServicePrincipalName, UserPrincipalName, Enabled, LastLogonDate, PasswordLastSet, DistinguishedName `
244+ - ErrorAction Stop
245+ )
246+
247+ if ($KerberoastableAccounts.Count -gt 0 ) {
248+ $KRBCsvPath = Join-Path $CurrentDCOutputDir " MSADPT_DC_Kerberoastable_${DCName} _$ScriptStartTime .csv"
249+
250+ $KerberoastableAccounts |
251+ Select-Object `
252+ Name,
253+ SamAccountName,
254+ UserPrincipalName,
255+ @ {
256+ Name = ' ServicePrincipalName'
257+ Expression = { @ ($_.ServicePrincipalName ) -join ' ;' }
258+ },
259+ Enabled,
260+ LastLogonDate,
261+ PasswordLastSet,
262+ DistinguishedName |
263+ Export-Csv - Path $KRBCsvPath - NoTypeInformation - Force
264+
265+ Write-MSADPTLog - Message " Identified $ ( $KerberoastableAccounts.Count ) Kerberoastable account(s). Details saved to '$KRBCsvPath '." - Level ' WARNING'
266+ }
267+ else {
268+ Write-MSADPTLog - Message " No Kerberoastable accounts found." - Level ' INFO'
269+ }
270+ }
271+ catch {
272+ Write-MSADPTLog - Message " Failed to identify Kerberoastable accounts from ${DCName} : $ ( $_.Exception.Message ) " - Level ' ERROR'
273+ $DCErrors += " Kerberoastable account discovery failed"
274+ }
275+ }
276+ else {
277+ Write-MSADPTLog - Message " Skipping Kerberoastable account identification for $DCName ." - Level ' INFO'
278+ }
279+
280+ # -----------------------------------------------------------------
281+ # 4. AS-REP Roastable Account Discovery
282+ # -----------------------------------------------------------------
283+ if (Prompt- User - PromptText " Identify AS-REP Roastable accounts (DONT_REQ_PREAUTH) from ${DCName} ?" ) {
284+ Write-MSADPTLog - Message " Identifying AS-REP Roastable accounts using explicit AD server '$AdServer '." - Level ' INFO'
285+
286+ try {
287+ # 4194304 / 0x400000 = DONT_REQ_PREAUTH
288+ $ASRepRoastableAccounts = @ (
289+ Get-ADUser @adSplat `
290+ - LDAPFilter ' (&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))' `
291+ - Properties UserPrincipalName, ServicePrincipalName, UserAccountControl, Enabled, LastLogonDate, PasswordLastSet, DistinguishedName `
292+ - ErrorAction Stop
293+ )
294+
295+ if ($ASRepRoastableAccounts.Count -gt 0 ) {
296+ $ASRCsvPath = Join-Path $CurrentDCOutputDir " MSADPT_DC_ASREPRoastable_${DCName} _$ScriptStartTime .csv"
297+
298+ $ASRepRoastableAccounts |
299+ Select-Object `
300+ Name,
301+ SamAccountName,
302+ UserPrincipalName,
303+ @ {
304+ Name = ' ServicePrincipalName'
305+ Expression = { @ ($_.ServicePrincipalName ) -join ' ;' }
306+ },
307+ UserAccountControl,
308+ Enabled,
309+ LastLogonDate,
310+ PasswordLastSet,
311+ DistinguishedName |
312+ Export-Csv - Path $ASRCsvPath - NoTypeInformation - Force
313+
314+ Write-MSADPTLog - Message " Identified $ ( $ASRepRoastableAccounts.Count ) AS-REP Roastable account(s). Details saved to '$ASRCsvPath '." - Level ' WARNING'
315+ }
316+ else {
317+ Write-MSADPTLog - Message " No AS-REP Roastable accounts found." - Level ' INFO'
318+ }
319+ }
320+ catch {
321+ Write-MSADPTLog - Message " Failed to identify AS-REP Roastable accounts from ${DCName} : $ ( $_.Exception.Message ) " - Level ' ERROR'
322+ $DCErrors += " AS-REP Roastable account discovery failed"
323+ }
324+ }
325+ else {
326+ Write-MSADPTLog - Message " Skipping AS-REP Roastable account identification for $DCName ." - Level ' INFO'
327+ }
328+
329+ # -----------------------------------------------------------------
330+ # 5. LAPS Configuration / Exposure Review
331+ # -----------------------------------------------------------------
332+ if (Prompt- User - PromptText " Enumerate LAPS configuration and readable password attributes from ${DCName} ?" ) {
333+ Write-MSADPTLog - Message " Querying LAPS-related computer attributes using explicit AD server '$AdServer '." - Level ' INFO'
334+
335+ try {
336+ $schemaNC = $rootDSE.schemaNamingContext
337+
338+ $HasWindowsLAPS = $false
339+ try {
340+ $HasWindowsLAPS = $null -ne (
341+ Get-ADObject @adSplat `
342+ - SearchBase $schemaNC `
343+ - LDAPFilter ' (lDAPDisplayName=msLAPS-Password)' `
344+ - ErrorAction SilentlyContinue
345+ )
346+ }
347+ catch {
348+ Write-MSADPTLog - Message " Could not confirm Windows LAPS schema support: $ ( $_.Exception.Message ) " - Level ' WARNING'
349+ $HasWindowsLAPS = $false
350+ }
351+
352+ $LAPSProperties = @ (
353+ ' ms-Mcs-AdmPwd' ,
354+ ' ms-Mcs-AdmPwdExpirationTime'
355+ )
356+
357+ $LAPSLdapFilter = ' (ms-Mcs-AdmPwdExpirationTime=*)'
358+
359+ if ($HasWindowsLAPS ) {
360+ $LAPSProperties += @ (
361+ ' msLAPS-Password' ,
362+ ' msLAPS-PasswordExpirationTime'
363+ )
364+
365+ $LAPSLdapFilter = ' (|(ms-Mcs-AdmPwdExpirationTime=*)(msLAPS-PasswordExpirationTime=*))'
366+ }
367+
368+ $LAPSComputers = @ (
369+ Get-ADComputer @adSplat `
370+ - LDAPFilter $LAPSLdapFilter `
371+ - Properties $LAPSProperties `
372+ - ErrorAction Stop
373+ )
374+
375+ $LAPSReaders = @ ()
376+ try {
377+ $LAPSReaders = @ (
378+ Get-ADGroup @adSplat `
379+ - Filter " Name -like '*LAPS*Reader*'" `
380+ - Properties Member `
381+ - ErrorAction Stop
382+ )
383+ }
384+ catch {
385+ Write-MSADPTLog - Message " Could not enumerate LAPS reader groups: $ ( $_.Exception.Message ) " - Level ' WARNING'
386+ $LAPSReaders = @ ()
387+ }
388+
389+ $LAPSInfo = [PSCustomObject ]@ {
390+ Timestamp = (Get-Date ).ToString(" yyyy-MM-dd HH:mm:ss" )
391+ DCName = $DCName
392+ WindowsLAPS_Supported = $HasWindowsLAPS
393+ LAPS_EnabledComputers = @ ($LAPSComputers ).Count
394+ LAPS_ComputerDetails = (
395+ $LAPSComputers |
396+ Select-Object `
397+ Name,
398+ DNSHostName,
399+ DistinguishedName,
400+ ' ms-Mcs-AdmPwd' ,
401+ ' ms-Mcs-AdmPwdExpirationTime' ,
402+ ' msLAPS-Password' ,
403+ ' msLAPS-PasswordExpirationTime' |
404+ ConvertTo-Json - Compress - Depth 5
405+ )
406+ LAPS_ReaderGroups = (
407+ $LAPSReaders |
408+ Select-Object `
409+ Name,
410+ SamAccountName,
411+ DistinguishedName,
412+ @ {
413+ Name = ' Members'
414+ Expression = { @ ($_.Member ) -join ' ;' }
415+ } |
416+ ConvertTo-Json - Compress - Depth 5
417+ )
418+ }
419+
420+ $LAPSCsvPath = Join-Path $CurrentDCOutputDir " MSADPT_DC_LAPSInfo_${DCName} _$ScriptStartTime .csv"
421+ $LAPSInfo | Export-Csv - Path $LAPSCsvPath - NoTypeInformation - Force - ErrorAction Stop
422+
423+ Write-MSADPTLog - Message " LAPS configuration details saved to '$LAPSCsvPath '. Password fields are populated only if the supplied credential has read rights." - Level ' WARNING'
424+ }
425+ catch {
426+ Write-MSADPTLog - Message " Failed to enumerate LAPS configuration from ${DCName} : $ ( $_.Exception.Message ) " - Level ' ERROR'
427+ $DCErrors += " LAPS enumeration failed"
428+ }
429+ }
430+ else {
431+ Write-MSADPTLog - Message " Skipping LAPS configuration enumeration for $DCName ." - Level ' INFO'
432+ }
175433
176434 # -----------------------------------------------------------------
177435 # Final DC summary
@@ -187,4 +445,4 @@ foreach ($DC in $DCs) {
187445 }
188446}
189447
190- Write-MSADPTLog - Message " MSADPT_enumerate_dc .ps1 completed." - Level ' INFO'
448+ Write-MSADPTLog - Message " MSADPT_enumerate_dc2 .ps1 completed." - Level ' INFO'
0 commit comments