· 8 years ago · Apr 11, 2018, 05:08 PM
1<#
2
3.SYNOPSIS
4 ADRecon is a tool which gathers information about the Active Directory and generates a report which can provide a holistic picture of the current state of the target AD environment.
5
6.DESCRIPTION
7
8 ADRecon is a tool which extracts various artifacts (as highlighted below) out of an AD environment in a specially formatted Microsoft Excel report that includes summary views with metrics to facilitate analysis.
9 The report can provide a holistic picture of the current state of the target AD environment.
10 The tool is useful to various classes of security professionals like auditors, DIFR, students, administrators, etc. It can also be an invaluable post-exploitation tool for a penetration tester.
11 It can be run from any workstation that is connected to the environment even hosts that are not domain members. Furthermore, the tool can be executed in the context of a non-privileged (i.e. standard domain user) accounts. Fine Grained Password Policy, LAPS and BitLocker may require Privileged user accounts.
12 The tool will use Microsoft Remote Server Administration Tools (RSAT) if available, otherwise it will communicate with the Domain Controller using LDAP.
13 The following information is gathered by the tool:
14 - Forest;
15 - Domains in the Forest and other attributes such as Sites;
16 - Domain Password Policy;
17 - Domain Controllers and their roles;
18 - Users and their attributes;
19 - Service Principal Names;
20 - Groups and and their members;
21 - Organizational Units and their ACLs;
22 - Group Policy Object details;
23 - DNS Zones and Records;
24 - Printers;
25 - Computers and their attributes;
26 - LAPS passwords (if implemented); and
27 - BitLocker Recovery Keys (if implemented).
28
29 Author : Prashant Mahajan
30 Company : https://www.senseofsecurity.com.au
31
32.NOTES
33
34 The following commands can be used to turn off ExecutionPolicy: (Requires Admin Privs)
35
36 PS > $ExecPolicy = Get-ExecutionPolicy
37 PS > Set-ExecutionPolicy bypass
38 PS > .\ADRecon.ps1
39 PS > Set-ExecutionPolicy $ExecPolicy
40
41 OR
42
43 Start the PowerShell as follows:
44 powershell.exe -ep bypass
45
46 OR
47
48 Already have a PowerShell open ?
49 PS > $Env:PSExecutionPolicyPreference = 'Bypass'
50
51 OR
52
53 powershell.exe -nologo -executionpolicy bypass -noprofile -file ADRecon.ps1
54
55.PARAMETER Protocol
56 Which protocol to use; ADWS (default) or LDAP
57
58.PARAMETER DomainController
59 Domain Controller IP Address or Domain FQDN.
60
61.PARAMETER Credential
62 Domain Credentials.
63
64.PARAMETER GenExcel
65 Path for ADRecon output folder containing the CSV files to generate the ADRecon-Report.xlsx. Use it to generate the ADRecon-Report.xlsx when Microsoft Excel is not installed on the host used to run ADRecon.
66
67.PARAMETER OutputDir
68 Path for ADRecon output folder to save the CSV files and the ADRecon-Report.xlsx. (The folder specified will be created if it doesn't exist)
69
70.PARAMETER Collect
71 What attributes to collect; Comma separated; e.g Forest,Domain (Default all)
72 Valid values include: Forest, Domain, PasswordPolicy, DCs, Users, UserSPNs, Groups, GroupMembers, OUs, OUPermissions, GPOs, GPOReport, DNSZones, Printers, Computers, ComputerSPNs, LAPS, BitLocker.
73
74.PARAMETER OutputType
75 Output Type; Comma seperated; e.g CSV,STDOUT,Excel (Default STDOUT with -Collect parameter, else CSV and Excel).
76 Valid values include: STDOUT, CSV, Excel.
77
78.PARAMETER DormantTimeSpan
79 Timespan for Dormant accounts. (Default 90 days)
80
81.PARAMETER PageSize
82 The PageSize to set for the LDAP searcher object.
83
84.PARAMETER Threads
85 The number of threads to use during processing objects. (Default 10)
86
87.PARAMETER FlushCount
88 The number of processed objects which will be flushed to disk. (Default -1; Flush after all objects are processed).
89
90.EXAMPLE
91
92 .\ADRecon.ps1 -GenExcel C:\ADRecon-Report-<timestamp>
93 [*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
94 [*] Generating ADRecon-Report.xlsx
95 [+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
96
97.EXAMPLE
98
99 .\ADRecon.ps1 -DomainController <IP or FQDN> -Credential <domain\username>
100 [*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
101 Member Workstation
102 <Domain>
103 <snip>
104
105 Example output from Domain Member with Alternate Credentials.
106
107.EXAMPLE
108
109 .\ADRecon.ps1 -DomainController <IP or FQDN> -Credential <domain\username> -Collect DCs -OutputType Excel
110 [*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
111 Standalone Workstation
112 WORKGROUP
113 [*] Commencing - <timestamp>
114 [-] Domain Controllers
115 [*] Total Execution Time (mins): <minutes>
116 [*] Generating ADRecon-Report.xlsx
117 [+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
118 [*] Completed.
119 [*] Output Directory: C:\ADRecon-Report-<timestamp>
120
121 Example output from from a Non-Member using RSAT to only enumerate Domain Controllers.
122
123.EXAMPLE
124
125 .\ADRecon.ps1 -Protocol ADWS -DomainController <IP or FQDN> -Credential <domain\username>
126 [*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
127 Standalone Workstation
128 WORKGROUP
129 [*] Commencing - <timestamp>
130 [-] Domain
131 [-] Forest
132 [-] Default Password Policy
133 [-] Fine Grained Password Policy - May need a Privileged Account
134 [-] Domain Controllers
135 [-] Domain Users - May take some time
136 [*] Total Users: <number>
137 [-] Domain User SPNs
138 [-] Domain Groups - May take some time
139 [*] Total Groups: <number>
140 [-] Domain Group Memberships - May take some time
141 [*] Total GroupMember Objects: <number>
142 [-] Domain OrganizationalUnits
143 [*] Total OUs: <number>
144 [-] Domain OrganizationalUnits Permissions - May take some time
145 [-] Domain GPOs
146 [*] Total GPOs: <number>
147 [-] Domain DNS Zones and Records
148 [*] Total DNS Zones: <number>
149 [-] Domain Printers
150 [*] Total Printers: <number>
151 [-] Domain Computers - May take some time
152 [*] Total Computers: <number>
153 [-] Domain Computer SPNs
154 [-] LAPS - Needs Privileged Account
155 [*] LAPS is not implemented.
156 [-] BitLocker Recovery Keys - Needs Privileged Account
157 [*] Total Execution Time (mins): <minutes>
158 [*] Generating ADRecon-Report.xlsx
159 [+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
160 [*] Completed.
161 [*] Output Directory: C:\ADRecon-Report-<timestamp>
162
163 Example output from a Non-Member using RSAT.
164
165.EXAMPLE
166
167 .\ADRecon.ps1 -Protocol LDAP -DomainController <IP or FQDN> -Credential <domain\username>
168 [*] ADRecon <version> by Prashant Mahajan (@prashant3535) from Sense of Security.
169 Standalone Workstation
170 WORKGROUP
171 [*] LDAP bind Successful
172 [*] Commencing - <timstamp>
173 [-] Domain
174 [-] Forest
175 [-] Default Password Policy
176 [-] Fine Grained Password Policy - May need a Privileged Account
177 [-] Domain Controllers
178 [-] Domain Users - May take some time
179 [*] Calculating if the user Cannot Change Password
180 [*] Total Users: <number>
181 [-] Domain User SPNs
182 [-] Domain Groups - May take some time
183 [*] Total Groups: <number>
184 [-] Domain Group Memberships - May take some time
185 [*] Total GroupMember Objects: <number>
186 [-] Domain OrganizationalUnits
187 [*] Total OUs: <number>
188 [-] Domain OrganizationalUnits Permissions - May take some time
189 [-] Domain GPOs
190 [*] Total GPOs: <number>
191 [-] Domain DNS Zones and Records
192 [*] Total DNS Zones: <number>
193 [-] Domain Printers
194 [*] Total Printers: <number>
195 [-] Domain Computers - May take some time
196 [*] Total Computers: <number>
197 [-] Domain Computer SPNs
198 [-] LAPS - Needs Privileged Account
199 [*] LAPS is not implemented.
200 [-] BitLocker Recovery Keys - Needs Privileged Account
201 [*] Total Execution Time (mins): <timestamp>
202 [*] Generating ADRecon-Report.xlsx
203 [+] Excelsheet Saved to: C:\ADRecon-Report-<timestamp>\<domain>-ADRecon-Report.xlsx
204 [*] Completed.
205 [*] Output Directory: C:\ADRecon-Report-<timestamp>
206
207 Example output from a Non-Member using LDAP.
208
209.LINK
210https://github.com/sense-of-security/ADRecon
211#>
212
213[CmdletBinding()]
214param
215(
216 [Parameter(Mandatory = $false, HelpMessage = "Which protocol to use; ADWS (default) or LDAP.")]
217 [string] $Protocol = 'ADWS',
218
219 [Parameter(Mandatory = $false, HelpMessage = "Domain Controller IP Address or Domain FQDN.")]
220 [string] $DomainController,
221
222 [Parameter(Mandatory = $false, HelpMessage = "Domain Credentials.")]
223 [Management.Automation.PSCredential] $Credential = [Management.Automation.PSCredential]::Empty,
224
225 [Parameter(Mandatory = $false, HelpMessage = "Path for ADRecon output folder containing the CSV files to generate the ADRecon-Report.xlsx. Use it to generate the ADRecon-Report.xlsx when Microsoft Excel is not installed on the host used to run ADRecon.")]
226 [string] $GenExcel,
227
228 [Parameter(Mandatory = $false, HelpMessage = "Path for ADRecon output folder to save the CSV files and the ADRecon-Report.xlsx. (The folder specified will be created if it doesn't exist)")]
229 [string] $OutputDir,
230
231 [Parameter(Mandatory = $false, HelpMessage = "What attributes to collect; Comma separated; e.g Forest,Domain (Default all) Valid values include: Forest, Domain, PasswordPolicy, DCs, Users, UserSPNs, Groups, GroupMembers, OUs, OUPermissions, GPOs, GPOReport, DNSZones, Printers, Computers, ComputerSPNs, LAPS, BitLocker")]
232 [ValidateSet('Forest', 'Domain', 'PasswordPolicy', 'DCs', 'Users', 'UserSPNs', 'Groups', 'GroupMembers', 'OUs', 'OUPermissions', 'GPOs', 'GPOReport', 'DNSZones', 'Printers', 'Computers', 'ComputerSPNs', 'LAPS', 'BitLocker', 'Default')]
233 [array] $Collect = 'Default',
234
235 [Parameter(Mandatory = $false, HelpMessage = "Output type; Comma seperated; e.g CSV,STDOUT,Excel (Default STDOUT with -Collect parameter, else CSV and Excel)")]
236 [ValidateSet('STDOUT', 'CSV', 'EXCEL', 'Default')]
237 [array] $OutputType = 'Default',
238
239 [Parameter(Mandatory = $false, HelpMessage = "Timespan for Dormant accounts. Default 90 days")]
240 [ValidateRange(1,1000)]
241 [int] $DormantTimeSpan = 90,
242
243 [Parameter(Mandatory = $false, HelpMessage = "The PageSize to set for the LDAP searcher object. Default 200")]
244 [ValidateRange(1,10000)]
245 [int] $PageSize = 200,
246
247 [Parameter(Mandatory = $false, HelpMessage = "The number of threads to use during processing of objects. Default 10")]
248 [ValidateRange(1,100)]
249 [int] $Threads = 10,
250
251 [Parameter(Mandatory = $false, HelpMessage = "The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).")]
252 [ValidateRange(-1,1000000)]
253 [int] $FlushCount = -1
254
255)
256
257$ADWSSource = @"
258// Thanks Dennis Albuquerque for the C# multithreading code
259using System;
260using System.Collections.Generic;
261using System.IO;
262using System.Threading;
263using System.Text.RegularExpressions;
264using System.Security.Principal;
265using System.Management.Automation;
266
267namespace ADRecon
268{
269 public static class ADWSClass
270 {
271 private static DateTime Date1;
272 private static int PassMaxAge;
273 private static int DormantTimeSpan;
274 private static string FilePath;
275 private static bool ADRSTDOUT = false;
276 private static bool ADRCSV = false;
277 private static readonly HashSet<string> Groups = new HashSet<string> ( new String[] {"268435456", "268435457", "536870912", "536870913"} );
278 private static readonly HashSet<string> Users = new HashSet<string> ( new String[] { "805306368" } );
279 private static readonly HashSet<string> Computers = new HashSet<string> ( new String[] { "805306369" }) ;
280 private static readonly HashSet<string> TrustAccounts = new HashSet<string> ( new String[] { "805306370" } );
281
282 private static readonly Dictionary<String, String> Replacements = new Dictionary<String, String>()
283 {
284 //{System.Environment.NewLine, ""},
285 //{",", ";"},
286 {"\"", "'"}
287 };
288
289 public static String CleanString(String StringtoClean)
290 {
291 // Remove extra spaces and new lines
292 String CleanedString = String.Join(" ", ((Convert.ToString(StringtoClean)).Split((string[]) null, StringSplitOptions.RemoveEmptyEntries)));
293 foreach (String Replacement in Replacements.Keys)
294 {
295 CleanedString = CleanedString.Replace(Replacement, Replacements[Replacement]);
296 }
297 return CleanedString;
298 }
299
300 public static void UserParser(Object[] AdUsers, DateTime Date1, int PassMaxAge, string FilePath, int DormantTimeSpan, int numOfThreads, int flushCnt, String[] OutputType)
301 {
302 ADWSClass.Date1 = Date1;
303 ADWSClass.PassMaxAge = PassMaxAge;
304 ADWSClass.DormantTimeSpan = DormantTimeSpan;
305 ADWSClass.FilePath = FilePath;
306
307 foreach (string OType in OutputType)
308 {
309 switch (OType)
310 {
311 case "STDOUT":
312 ADWSClass.ADRSTDOUT = true;
313 continue;
314 case "CSV":
315 ADWSClass.ADRCSV = true;
316 break;
317 }
318 }
319 if (ADWSClass.ADRCSV == true)
320 {
321 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
322 {
323 string HeaderRow = String.Format("Name,UserName,Enabled,Cannot Change Password,Password Never Expires,Must Change Password at Logon,Days Since Last Password Change,Password Not Changed after Max Age,Account Locked Out,Never Logged in,Days Since Last Logon,Dormant (> {0} days),Reversibly Encryped Password,Password Not Required,Trusted for Delegation,Trusted to Auth for Delegation,Does Not Require Pre Auth,Logon Workstations,AdminCount,Primary GroupID,SID,SIDHistory,Description,Password LastSet,Last Logon Date,When Created,When Changed,DistinguishedName,CanonicalName",DormantTimeSpan);
324 file.WriteLine(HeaderRow);
325 }
326 }
327 Console.WriteLine("[*] Total Users: " + AdUsers.Length);
328 runProcessor(AdUsers, numOfThreads, flushCnt, "Users", "CSV");
329 }
330
331 public static int UserSPNParser(Object[] AdUsers, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
332 {
333 if (AdUsers.Length == 1)
334 {
335 return AdUsers.Length;
336 }
337
338 ADWSClass.FilePath = FilePath;
339
340 foreach (string OType in OutputType)
341 {
342 switch (OType)
343 {
344 case "STDOUT":
345 ADWSClass.ADRSTDOUT = true;
346 continue;
347 case "CSV":
348 ADWSClass.ADRCSV = true;
349 break;
350 }
351 }
352 if (ADWSClass.ADRCSV == true)
353 {
354 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
355 {
356 string HeaderRow = "Name,Username,Service,Host,Password Last Set,Description";
357 file.WriteLine(HeaderRow);
358 }
359 }
360 runProcessor(AdUsers, numOfThreads, flushCnt, "UserSPNs", "CSV");
361 return AdUsers.Length;
362 }
363
364 public static void GroupParser(Object[] AdGroups, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
365 {
366 ADWSClass.FilePath = FilePath;
367 foreach (string OType in OutputType)
368 {
369 switch (OType)
370 {
371 case "STDOUT":
372 ADWSClass.ADRSTDOUT = true;
373 continue;
374 case "CSV":
375 ADWSClass.ADRCSV = true;
376 break;
377 }
378 }
379 if (ADWSClass.ADRCSV == true)
380 {
381 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
382 {
383 string HeaderRow = "Group,ManagedBy,whenCreated,whenChanged,Description,SID,DistinguishedName,CanonicalName";
384 file.WriteLine(HeaderRow);
385 }
386 }
387 Console.WriteLine("[*] Total Groups: " + AdGroups.Length);
388 runProcessor(AdGroups, numOfThreads, flushCnt, "Groups", "CSV");
389 }
390
391 public static void GroupMemberParser(Object[] AdGroupMembers, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
392 {
393 ADWSClass.FilePath = FilePath;
394
395 foreach (string OType in OutputType)
396 {
397 switch (OType)
398 {
399 case "STDOUT":
400 ADWSClass.ADRSTDOUT = true;
401 continue;
402 case "CSV":
403 ADWSClass.ADRCSV = true;
404 break;
405 }
406 }
407 if (ADWSClass.ADRCSV == true)
408 {
409 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
410 {
411 string HeaderRow = "Group Name, Member UserName, Member Name, AccountType";
412 file.WriteLine(HeaderRow);
413 }
414 }
415 Console.WriteLine("[*] Total GroupMember Objects: " + AdGroupMembers.Length);
416 runProcessor(AdGroupMembers, numOfThreads, flushCnt, "GroupMembers", "CSV");
417 }
418
419 public static int ComputerParser(Object[] AdComputers, DateTime Date1, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
420 {
421 Console.WriteLine("[*] Total Computers: " + AdComputers.Length);
422 if (AdComputers.Length == 1)
423 {
424 return AdComputers.Length;
425 }
426
427 ADWSClass.Date1 = Date1;
428 ADWSClass.FilePath = FilePath;
429
430 foreach (string OType in OutputType)
431 {
432 switch (OType)
433 {
434 case "STDOUT":
435 ADWSClass.ADRSTDOUT = true;
436 continue;
437 case "CSV":
438 ADWSClass.ADRCSV = true;
439 break;
440 }
441 }
442 if (ADWSClass.ADRCSV == true)
443 {
444 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
445 {
446 string HeaderRow = "Name,DNSHostName,Enabled,IPv4Address,OperatingSystem,Days Since Last Logon,Days Since Last Password Change,Trusted for Delegation,Trusted to Auth for Delegation,Username,Primary Group ID,Description,Password LastSet,Last Logon Date,whenCreated,whenChanged,Distinguished Name";
447 file.WriteLine(HeaderRow);
448 }
449 }
450 runProcessor(AdComputers, numOfThreads, flushCnt, "Computers", "CSV");
451 return AdComputers.Length;
452 }
453
454 public static int ComputerSPNParser(Object[] AdComputers, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
455 {
456 if (AdComputers.Length == 1)
457 {
458 return AdComputers.Length;
459 }
460
461 ADWSClass.FilePath = FilePath;
462
463 foreach (string OType in OutputType)
464 {
465 switch (OType)
466 {
467 case "STDOUT":
468 ADWSClass.ADRSTDOUT = true;
469 continue;
470 case "CSV":
471 ADWSClass.ADRCSV = true;
472 break;
473 }
474 }
475 if (ADWSClass.ADRCSV == true)
476 {
477 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
478 {
479 string HeaderRow = "Name,Service,Host";
480 file.WriteLine(HeaderRow);
481 }
482 }
483 runProcessor(AdComputers, numOfThreads, flushCnt, "ComputerSPNs", "CSV");
484 return AdComputers.Length;
485 }
486
487 static void runProcessor(Object[] arrayToProcess, int numOfThreads, int flushCnt, string processorType, String resultHandlerType)
488 {
489 int totalRecords = arrayToProcess.Length;
490 //Console.WriteLine(String.Format("Running {0} records over {1} threads, flushing every {2} records",
491 // totalRecords, numOfThreads, (flushCnt < 0 ? "NEVER" : flushCnt.ToString())));
492 IRecordProcessor recordProcessor = recordProcessorFactory(processorType);
493 IResultsHandler resultsHandler = resultHandlerFactory(resultHandlerType, flushCnt);
494 int numberOfRecordsPerThread = totalRecords / numOfThreads;
495 int remainders = totalRecords % numOfThreads;
496
497 Thread[] threads = new Thread[numOfThreads];
498 for (int i = 0; i < numOfThreads; i++)
499 {
500 int numberOfRecordsToProcess = numberOfRecordsPerThread;
501 if (i == (numOfThreads - 1))
502 {
503 //last thread, do the remaining records
504 numberOfRecordsToProcess += remainders;
505 }
506
507 //split the full array into chunks to be given to different threads
508 Object[] sliceToProcess = new Object[numberOfRecordsToProcess];
509 Array.Copy(arrayToProcess, i * numberOfRecordsPerThread, sliceToProcess, 0, numberOfRecordsToProcess);
510 ProcessorThread processorThread = new ProcessorThread(i, recordProcessor, resultsHandler, sliceToProcess);
511 threads[i] = new Thread(processorThread.processThreadRecords);
512 threads[i].Start();
513 }
514 foreach (Thread t in threads)
515 {
516 t.Join();
517 }
518
519 resultsHandler.finalise();
520 }
521
522 static IRecordProcessor recordProcessorFactory(String name)
523 {
524 switch (name)
525 {
526 case "Users":
527 return new UserRecordProcessor();
528 case "UserSPNs":
529 return new UserSPNRecordProcessor();
530 case "Groups":
531 return new GroupRecordProcessor();
532 case "GroupMembers":
533 return new GroupMemberRecordProcessor();
534 case "Computers":
535 return new ComputerRecordProcessor();
536 case "ComputerSPNs":
537 return new ComputerSPNRecordProcessor();
538 }
539 throw new ArgumentException("Invalid processor type " + name);
540 }
541
542 static IResultsHandler resultHandlerFactory(String name, int flushCnt)
543 {
544 switch (name)
545 {
546 case "CSV":
547 return new CsvResultsHandler(flushCnt);
548 case "TXT":
549 return new TxtResultsHandler(flushCnt);
550 }
551 throw new ArgumentException("Invalid processor type " + name);
552 }
553
554 class ProcessorThread
555 {
556 readonly int id;
557 readonly IRecordProcessor recordProcessor;
558 readonly IResultsHandler resultsHandler;
559 readonly Object[] objectsToBeProcessed;
560
561 public ProcessorThread(int id, IRecordProcessor recordProcessor, IResultsHandler resultsHandler, Object[] objectsToBeProcessed)
562 {
563 this.recordProcessor = recordProcessor;
564 this.id = id;
565 this.resultsHandler = resultsHandler;
566 this.objectsToBeProcessed = objectsToBeProcessed;
567 }
568
569 public void processThreadRecords()
570 {
571 for (int i = 0; i < objectsToBeProcessed.Length; i++)
572 {
573 Object[] result = recordProcessor.processRecord(objectsToBeProcessed[i]);
574 resultsHandler.processResults(result); //this is a thread safe operation
575 }
576 }
577 }
578
579 //The interface and implmentation class used to process a record (this implemmentation just returns a log type string)
580
581 interface IRecordProcessor
582 {
583 Object[] processRecord(Object record);
584 }
585
586 class UserRecordProcessor : IRecordProcessor
587 {
588 public Object[] processRecord(Object record)
589 {
590 try
591 {
592 PSObject AdUser = (PSObject) record;
593 bool? Enabled = null;
594 bool MustChangePasswordatLogon = false;
595 int DaysSinceLastPasswordChange = -1;
596 bool PasswordNotChangedafterMaxAge = false;
597 bool NeverLoggedIn = false;
598 int DaysSinceLastLogon = -1;
599 bool Dormant = false;
600 String SIDHistory = "";
601 DateTime PasswordLastSet = Convert.ToDateTime(AdUser.Members["PasswordLastSet"].Value);
602 try
603 {
604 // The Enabled field can be blank which raises an exception. This may occur when the user is not allowed to query the UserAccountControl attribute.
605 Enabled = (bool) AdUser.Members["Enabled"].Value;
606 }
607 catch //(Exception e)
608 {
609 // Console.WriteLine("{0} Exception caught.", e);
610 }
611 if (Convert.ToString(AdUser.Members["pwdlastset"].Value) == "0")
612 {
613 MustChangePasswordatLogon = true;
614 }
615 else
616 {
617 DaysSinceLastPasswordChange = Math.Abs((Date1 - PasswordLastSet).Days);
618 if (DaysSinceLastPasswordChange > PassMaxAge)
619 {
620 PasswordNotChangedafterMaxAge = true;
621 }
622 }
623 DateTime LastLogonDate = Convert.ToDateTime(AdUser.Members["LastLogonDate"].Value);
624 if (AdUser.Members["LastLogonDate"].Value != null)
625 {
626 DaysSinceLastLogon = Math.Abs((Date1 - LastLogonDate).Days);
627 if (DaysSinceLastLogon > DormantTimeSpan)
628 {
629 Dormant = true;
630 }
631 }
632 else
633 {
634 NeverLoggedIn = true;
635 }
636 Microsoft.ActiveDirectory.Management.ADPropertyValueCollection history = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection) AdUser.Members["SIDHistory"].Value;
637 if (history.Value is System.Security.Principal.SecurityIdentifier[])
638 {
639 string sids = "";
640 foreach (var value in (SecurityIdentifier[]) history.Value)
641 {
642 sids = sids + "," + Convert.ToString(value);
643 }
644 SIDHistory = sids.TrimStart(',');
645 }
646 else
647 {
648 SIDHistory = history != null ? Convert.ToString(history.Value) : "";
649 }
650 if (ADWSClass.ADRSTDOUT == true)
651 {
652 Console.WriteLine("\nName{0,33}{2}\nUserName{0,29}{3}\nEnabled{0,30}{4}\nCannot Change Password{0,15}{5}\nPassword Never Expires{0,15}{6}\nMust Change Password at Logon{0,8}{7}\nDays Since Last Password Change{0,6}{8}\nPassword Not Changed after Max Age{0}{9}\nAccount Locked Out{0,19}{10}\nNever Logged in{0,22}{11}\nDays Since Last Logon{0,16}{12}\nDormant (> {1} days){0,18}{13}\nReversibly Encryped Password{0,9}{14}\nPassword Not Required{0,16}{15}\nTrusted for Delegation{0,15}{16}\nTrusted to Auth for Delegation{0,7}{17}\nDoes Not Require Pre Auth{0,12}{18}\nLogon Workstations{0,19}{19}\nAdminCount{0,27}{20}\nPrimary GroupID{0,22}{21}\nSID{0,34}{22}\nSIDHistory{0,27}{23}\nDescription{0,26}{24}\nPassword LastSet{0,21}{25}\nLast Logon Date{0,22}{26}\nWhen Created{0,25}{27}\nWhen Changed{0,25}{28}\nDistinguishedName{0,20}{29}\nCanonicalName{0,24}{30}\n", " : ", DormantTimeSpan, AdUser.Members["Name"].Value, AdUser.Members["SamAccountName"].Value, Enabled, AdUser.Members["CannotChangePassword"].Value, AdUser.Members["PasswordNeverExpires"].Value, MustChangePasswordatLogon, DaysSinceLastPasswordChange, PasswordNotChangedafterMaxAge, AdUser.Members["LockedOut"].Value, NeverLoggedIn, DaysSinceLastLogon, Dormant, AdUser.Members["AllowReversiblePasswordEncryption"].Value, AdUser.Members["PasswordNotRequired"].Value, AdUser.Members["TrustedForDelegation"].Value, AdUser.Members["TrustedToAuthForDelegation"].Value, AdUser.Members["DoesNotRequirePreAuth"].Value, AdUser.Members["LogonWorkstations"].Value, AdUser.Members["AdminCount"].Value, AdUser.Members["primaryGroupID"].Value, AdUser.Members["SID"].Value, SIDHistory, AdUser.Members["Description"].Value, PasswordLastSet, LastLogonDate, AdUser.Members["whenCreated"].Value, AdUser.Members["whenChanged"].Value, AdUser.Members["DistinguishedName"].Value, AdUser.Members["CanonicalName"].Value);
653 }
654 return new Object[] { AdUser.Members["Name"].Value, AdUser.Members["SamAccountName"].Value, Enabled, AdUser.Members["CannotChangePassword"].Value, AdUser.Members["PasswordNeverExpires"].Value, MustChangePasswordatLogon, DaysSinceLastPasswordChange, PasswordNotChangedafterMaxAge, AdUser.Members["LockedOut"].Value, NeverLoggedIn, DaysSinceLastLogon, Dormant, AdUser.Members["AllowReversiblePasswordEncryption"].Value, AdUser.Members["PasswordNotRequired"].Value, AdUser.Members["TrustedForDelegation"].Value, AdUser.Members["TrustedToAuthForDelegation"].Value, AdUser.Members["DoesNotRequirePreAuth"].Value, AdUser.Members["LogonWorkstations"].Value, AdUser.Members["AdminCount"].Value, AdUser.Members["primaryGroupID"].Value, AdUser.Members["SID"].Value, SIDHistory, AdUser.Members["Description"].Value, PasswordLastSet, LastLogonDate, AdUser.Members["whenCreated"].Value, AdUser.Members["whenChanged"].Value, AdUser.Members["DistinguishedName"].Value, AdUser.Members["CanonicalName"].Value };
655 }
656 catch (Exception e)
657 {
658 Console.WriteLine("{0} Exception caught.", e);
659 return new Object[] { };
660 }
661 }
662 }
663
664 class UserSPNRecordProcessor : IRecordProcessor
665 {
666 public Object[] processRecord(Object record)
667 {
668 try
669 {
670 PSObject AdUser = (PSObject) record;
671 List<Object> SPNList = new List<Object>();
672 DateTime PasswordLastSet = DateTime.FromFileTime((long)AdUser.Members["pwdLastSet"].Value);
673 Microsoft.ActiveDirectory.Management.ADPropertyValueCollection SPNs = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdUser.Members["servicePrincipalName"].Value;
674 if (SPNs.Value is System.String[])
675 {
676 foreach (String SPN in (System.String[])SPNs.Value)
677 {
678 String[] SPNArray = SPN.Split('/');
679 SPNList.Add(new Object[] { AdUser.Members["Name"].Value, AdUser.Members["SamAccountName"].Value, SPNArray[0], SPNArray[1], PasswordLastSet, AdUser.Members["Description"].Value });
680 }
681 }
682 else
683 {
684 String[] SPNArray = Convert.ToString(SPNs.Value).Split('/');
685 SPNList.Add(new Object[] { AdUser.Members["Name"].Value, AdUser.Members["SamAccountName"].Value, SPNArray[0], SPNArray[1], PasswordLastSet, AdUser.Members["Description"].Value });
686 }
687 if (ADWSClass.ADRSTDOUT == true)
688 {
689 foreach (Object[] Obj in SPNList.ToArray())
690 {
691 Console.WriteLine("Name{0,16}{1}\nUsername{0,12}{2}\nService{0,13}{3}\nHost{0,16}{4}\nPassword Last Set{0}{5}\nDescription{0,9}{6}\n", " : ", Obj[0], Obj[1], Obj[2], Obj[3], Obj[4], Obj[5]);
692 }
693 }
694 return SPNList.ToArray();
695 }
696 catch (Exception e)
697 {
698 Console.WriteLine("{0} Exception caught.", e);
699 return new Object[] { };
700 }
701 }
702 }
703
704 class GroupRecordProcessor : IRecordProcessor
705 {
706 public Object[] processRecord(Object record)
707 {
708 try
709 {
710 PSObject AdGroup = (PSObject) record;
711 string ManagedByValue = Convert.ToString(AdGroup.Members["managedBy"].Value);
712 string ManagedBy = "";
713 if (AdGroup.Members["managedBy"].Value != null)
714 {
715 ManagedBy = (ManagedByValue.Split(',')[0]).Split('=')[1];
716 }
717 if (ADWSClass.ADRSTDOUT == true)
718 {
719 Console.WriteLine("Group{0,15}{1}\nManagedBy{0,11}{2}\nwhenCreated{0,9}{3}\nwhenChanged{0,9}{4}\nDescription{0,9}{5}\nSID{0,17}{6}\nDistinguishedName{0}{7}\nCanonicalName{0,7}{8}\n", " : ", AdGroup.Members["SamAccountName"].Value, ManagedBy, AdGroup.Members["whenCreated"].Value, AdGroup.Members["whenChanged"].Value, AdGroup.Members["Description"].Value, AdGroup.Members["sid"].Value, AdGroup.Members["DistinguishedName"].Value, AdGroup.Members["CanonicalName"].Value);
720 }
721 return new Object[] { AdGroup.Members["SamAccountName"].Value, ManagedBy, AdGroup.Members["whenCreated"].Value, AdGroup.Members["whenChanged"].Value, AdGroup.Members["Description"].Value, AdGroup.Members["sid"].Value, AdGroup.Members["DistinguishedName"].Value, AdGroup.Members["CanonicalName"].Value };
722 }
723 catch (Exception e)
724 {
725 Console.WriteLine("{0} Exception caught.", e);
726 return new Object[] { };
727 }
728 }
729 }
730
731 class GroupMemberRecordProcessor : IRecordProcessor
732 {
733 public Object[] processRecord(Object record)
734 {
735 try
736 {
737 // based on https://github.com/BloodHoundAD/BloodHound/blob/master/PowerShell/BloodHound.ps1
738 PSObject AdGroup = (PSObject) record;
739 List<Object> GroupsList = new List<Object>();
740 string SamAccountType = Convert.ToString(AdGroup.Members["samaccounttype"].Value);
741 string AccountType = "";
742 string GroupName = "";
743 string MemberUserName = "-";
744 string MemberName = "";
745 if (Groups.Contains(SamAccountType))
746 {
747 AccountType = "group";
748 MemberName = ((Convert.ToString(AdGroup.Members["DistinguishedName"].Value)).Split(',')[0]).Split('=')[1];
749 Microsoft.ActiveDirectory.Management.ADPropertyValueCollection MemberGroups = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdGroup.Members["memberof"].Value;
750 if (AdGroup.Members["memberof"].Value != null)
751 {
752 if (MemberGroups.Value is System.String[])
753 {
754 foreach (String GroupMember in (System.String[])MemberGroups.Value)
755 {
756 GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
757 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
758 }
759 }
760 else
761 {
762 GroupName = (Convert.ToString(MemberGroups.Value).Split(',')[0]).Split('=')[1];
763 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
764 }
765 }
766 }
767 if (Users.Contains(SamAccountType))
768 {
769 AccountType = "user";
770 MemberName = ((Convert.ToString(AdGroup.Members["DistinguishedName"].Value)).Split(',')[0]).Split('=')[1];
771 MemberUserName = Convert.ToString(AdGroup.Members["sAMAccountName"].Value);
772 Microsoft.ActiveDirectory.Management.ADPropertyValueCollection MemberGroups = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdGroup.Members["memberof"].Value;
773 if (AdGroup.Members["memberof"].Value != null)
774 {
775 if (MemberGroups.Value is System.String[])
776 {
777 foreach (String GroupMember in (System.String[])MemberGroups.Value)
778 {
779 GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
780 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
781 }
782 }
783 else
784 {
785 GroupName = (Convert.ToString(MemberGroups.Value).Split(',')[0]).Split('=')[1];
786 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
787 }
788 }
789 }
790 if (Computers.Contains(SamAccountType))
791 {
792 AccountType = "computer";
793 MemberName = ((Convert.ToString(AdGroup.Members["DistinguishedName"].Value)).Split(',')[0]).Split('=')[1];
794 MemberUserName = Convert.ToString(AdGroup.Members["sAMAccountName"].Value);
795 Microsoft.ActiveDirectory.Management.ADPropertyValueCollection MemberGroups = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdGroup.Members["memberof"].Value;
796 if (AdGroup.Members["memberof"].Value != null)
797 {
798 if (MemberGroups.Value is System.String[])
799 {
800 foreach (String GroupMember in (System.String[])MemberGroups.Value)
801 {
802 GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
803 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
804 }
805 }
806 else
807 {
808 GroupName = (Convert.ToString(MemberGroups.Value).Split(',')[0]).Split('=')[1];
809 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
810 }
811 }
812 }
813 if (TrustAccounts.Contains(SamAccountType))
814 {
815 // TO DO
816 }
817 if (ADWSClass.ADRSTDOUT == true)
818 {
819 foreach (Object[] Obj in GroupsList.ToArray())
820 {
821 Console.WriteLine("Group Name{0,8}{1}\nMember UserName{0}{2}\nMember Name{0,7}{3}\nAccountType{0,7}{4}\n", " : ", Obj[0], Obj[1], Obj[2], Obj[3]);
822 }
823 }
824 return GroupsList.ToArray();
825 }
826 catch (Exception e)
827 {
828 Console.WriteLine("{0} Exception caught.", e);
829 return new Object[] { };
830 }
831 }
832 }
833
834 class ComputerRecordProcessor : IRecordProcessor
835 {
836 public Object[] processRecord(Object record)
837 {
838 try
839 {
840 PSObject AdComputer = (PSObject) record;
841 int DaysSinceLastPasswordChange = -1;
842 int DaysSinceLastLogon = -1;
843 DateTime LastLogonDate = Convert.ToDateTime(AdComputer.Members["LastLogonDate"].Value);
844 if (AdComputer.Members["LastLogonDate"].Value != null)
845 {
846 DaysSinceLastLogon = Math.Abs((Date1 - LastLogonDate).Days);
847 }
848 DateTime PasswordLastSet = Convert.ToDateTime(AdComputer.Members["PasswordLastSet"].Value);
849 if (AdComputer.Members["PasswordLastSet"].Value != null)
850 {
851 DaysSinceLastPasswordChange = Math.Abs((Date1 - PasswordLastSet).Days);
852 }
853 if (ADWSClass.ADRSTDOUT == true)
854 {
855 Console.WriteLine("Name{0,30}{1}\nDNSHostName{0,23}{2}\nEnabled{0,27}{3}\nIPv4Address{0,23}{4}\nOperatingSystem{0,19}{5}\nDays Since Last Logon{0,13}{6}\nDays Since Last Password Change{0}{7}\nTrusted for Delegation{0,12}{8}\nTrusted to Auth for Delegation{0,4}{9}\nUsername{0,26}{10}\nPrimary Group ID{0,18}{11}\nDescription{0,23}{12}\nPassword LastSet{0,18}{13}\nLast Logon Date{0,19}{14}\nwhenCreated{0,23}{15}\nwhenChanged{0,23}{16}\nDistinguished Name{0,16}{17}\n", " : ", AdComputer.Members["Name"].Value, AdComputer.Members["DNSHostName"].Value, AdComputer.Members["Enabled"].Value, AdComputer.Members["IPv4Address"].Value, (AdComputer.Members["OperatingSystem"].Value != null ? AdComputer.Members["OperatingSystem"].Value : "-"), DaysSinceLastLogon, DaysSinceLastPasswordChange, AdComputer.Members["TrustedForDelegation"].Value, AdComputer.Members["TrustedToAuthForDelegation"].Value, AdComputer.Members["SamAccountName"].Value, AdComputer.Members["primaryGroupID"].Value, AdComputer.Members["Description"].Value, PasswordLastSet, LastLogonDate, AdComputer.Members["whenCreated"].Value, AdComputer.Members["whenChanged"].Value, AdComputer.Members["DistinguishedName"].Value);
856 }
857 return new Object[] { AdComputer.Members["Name"].Value, AdComputer.Members["DNSHostName"].Value, AdComputer.Members["Enabled"].Value, AdComputer.Members["IPv4Address"].Value, (AdComputer.Members["OperatingSystem"].Value != null ? AdComputer.Members["OperatingSystem"].Value : "-"), DaysSinceLastLogon, DaysSinceLastPasswordChange, AdComputer.Members["TrustedForDelegation"].Value, AdComputer.Members["TrustedToAuthForDelegation"].Value, AdComputer.Members["SamAccountName"].Value, AdComputer.Members["primaryGroupID"].Value, AdComputer.Members["Description"].Value, PasswordLastSet, LastLogonDate, AdComputer.Members["whenCreated"].Value, AdComputer.Members["whenChanged"].Value, AdComputer.Members["DistinguishedName"].Value };
858 }
859 catch (Exception e)
860 {
861 Console.WriteLine("{0} Exception caught.", e);
862 return new Object[] { };
863 }
864 }
865 }
866
867 class ComputerSPNRecordProcessor : IRecordProcessor
868 {
869 public Object[] processRecord(Object record)
870 {
871 try
872 {
873 PSObject AdComputer = (PSObject) record;
874 List<Object> SPNList = new List<Object>();
875 Microsoft.ActiveDirectory.Management.ADPropertyValueCollection SPNs = (Microsoft.ActiveDirectory.Management.ADPropertyValueCollection)AdComputer.Members["servicePrincipalName"].Value;
876 if (SPNs.Value is System.String[])
877 {
878 foreach (String SPN in (System.String[])SPNs.Value)
879 {
880 String[] SPNArray = SPN.Split('/');
881 SPNList.Add(new Object[] { AdComputer.Members["Name"].Value, SPNArray[0], SPNArray[1] });
882 }
883 }
884 else
885 {
886 String[] SPNArray = Convert.ToString(SPNs.Value).Split('/');
887 SPNList.Add(new Object[] { AdComputer.Members["Name"].Value, SPNArray[0], SPNArray[1] });
888 }
889 if (ADWSClass.ADRSTDOUT == true)
890 {
891 foreach (Object[] Obj in SPNList.ToArray())
892 {
893 Console.WriteLine("Name{0,6}{1}\nService{0}{2}\nHost{0,6}{3}\n", " : ", Obj[0], Obj[1], Obj[2]);
894 }
895 }
896 return SPNList.ToArray();
897 }
898 catch (Exception e)
899 {
900 Console.WriteLine("{0} Exception caught.", e);
901 return new Object[] { };
902 }
903 }
904 }
905
906 //The interface and implmentation class used to handle the results (this implementation just writes the strings to a file)
907
908 interface IResultsHandler
909 {
910 void processResults(Object[] t);
911
912 void finalise();
913 }
914
915 abstract class SimpleResultsHandler : IResultsHandler
916 {
917 private Object lockObj = new Object();
918 private List<String> processed = new List<String>();
919 private readonly int flushCnt;
920
921 public SimpleResultsHandler(int flushCnt)
922 {
923 this.flushCnt = flushCnt;
924 }
925
926 public void processResults(Object[] results)
927 {
928 lock (lockObj)
929 {
930 if (results.Length != 0)
931 {
932 if (results[0] is System.Object[])
933 {
934 for (var i = 0; i < results.Length; i++)
935 {
936 processed.Add(convertObject((Object[])results[i]));
937 }
938 }
939 else
940 {
941 processed.Add(convertObject(results));
942 }
943 if (flushCnt > 0)
944 {
945 if (processed.Count >= flushCnt)
946 {
947 writeFile();
948 }
949 }
950 }
951 }
952 }
953
954 public void finalise()
955 {
956 writeFile();
957 }
958
959 private void writeFile()
960 {
961 lock (lockObj)
962 {
963 if (ADWSClass.ADRCSV == true)
964 {
965 using (StreamWriter outputFile = new StreamWriter(@ADWSClass.FilePath, true))
966 {
967 outputFile.Write(String.Join("\r\n", processed.ToArray()));
968 }
969 }
970 processed.Clear();
971 }
972 }
973
974 protected abstract String convertObject(Object[] resultsObject);
975 }
976
977
978 class CsvResultsHandler : SimpleResultsHandler
979 {
980 public CsvResultsHandler(int flushCnt) : base(flushCnt)
981 {
982 }
983
984 protected override String convertObject(Object[] resultsObject)
985 {
986 return createCsvLine(resultsObject);
987 }
988
989 static String createCsvLine(Object[] resultsObject)
990 {
991 try
992 {
993 // No String.Join(String, Object[]) in CLR 2.0.50727 (Windows 7)
994 String[] row = new String[resultsObject.Length];
995 for (int i=0; i < resultsObject.Length; i++)
996 {
997 //String StringtoClean = Regex.Replace(Convert.ToString(resultsObject[i]),@"[^\S ]+", "");
998 row[i] = CleanString(Convert.ToString(resultsObject[i]));
999 }
1000 return "\"" + String.Join("\",\"", row) + "\"";
1001 }
1002 catch (Exception e)
1003 {
1004 Console.WriteLine("{0} Exception caught.", e);
1005 return "";
1006 }
1007 }
1008 }
1009
1010 class TxtResultsHandler : SimpleResultsHandler
1011 {
1012 public TxtResultsHandler(int flushCnt) : base(flushCnt)
1013 {
1014 }
1015
1016 protected override String convertObject(Object[] resultsObject)
1017 {
1018 try
1019 {
1020 // No String.Join(String, Object[]) in CLR 2.0.50727 (Windows 7)
1021 String[] row = new String[resultsObject.Length];
1022 for (int i=0; i < resultsObject.Length; i++)
1023 {
1024 //String StringtoClean = Regex.Replace(Convert.ToString(resultsObject[i]),@"[^\S ]+", "");
1025 row[i] = CleanString(Convert.ToString(resultsObject[i]));
1026 }
1027 return String.Join("\t", row);
1028 }
1029 catch (Exception e)
1030 {
1031 Console.WriteLine("{0} Exception caught.", e);
1032 return "";
1033 }
1034 }
1035 }
1036 }
1037}
1038"@
1039
1040$LDAPSource = @"
1041// Thanks Dennis Albuquerque for the C# multithreading code
1042using System;
1043using System.Collections.Generic;
1044using System.IO;
1045using System.Threading;
1046using System.Text.RegularExpressions;
1047using System.DirectoryServices;
1048using System.Security.Principal;
1049
1050namespace ADRecon
1051{
1052 public static class LDAPClass
1053 {
1054 private static DateTime Date1;
1055 private static int PassMaxAge;
1056 private static int DormantTimeSpan;
1057 private static string FilePath;
1058 private static bool ADRSTDOUT = false;
1059 private static bool ADRCSV = false;
1060 private static Dictionary<string, bool> CannotChangePasswordDict;
1061 private static readonly HashSet<string> Groups = new HashSet<string> ( new String[] {"268435456", "268435457", "536870912", "536870913"} );
1062 private static readonly HashSet<string> Users = new HashSet<string> ( new String[] { "805306368" } );
1063 private static readonly HashSet<string> Computers = new HashSet<string> ( new String[] { "805306369" }) ;
1064 private static readonly HashSet<string> TrustAccounts = new HashSet<string> ( new String[] { "805306370" } );
1065
1066 [Flags]
1067 //Values taken from https://support.microsoft.com/en-au/kb/305144
1068 public enum UACFlags
1069 {
1070 SCRIPT = 1, // 0x1
1071 ACCOUNTDISABLE = 2, // 0x2
1072 HOMEDIR_REQUIRED = 8, // 0x8
1073 LOCKOUT = 16, // 0x10
1074 PASSWD_NOTREQD = 32, // 0x20
1075 PASSWD_CANT_CHANGE = 64, // 0x40
1076 ENCRYPTED_TEXT_PASSWORD_ALLOWED = 128, // 0x80
1077 TEMP_DUPLICATE_ACCOUNT = 256, // 0x100
1078 NORMAL_ACCOUNT = 512, // 0x200
1079 INTERDOMAIN_TRUST_ACCOUNT = 2048, // 0x800
1080 WORKSTATION_TRUST_ACCOUNT = 4096, // 0x1000
1081 SERVER_TRUST_ACCOUNT = 8192, // 0x2000
1082 DONT_EXPIRE_PASSWD = 65536, // 0x10000
1083 MNS_LOGON_ACCOUNT = 131072, // 0x20000
1084 SMARTCARD_REQUIRED = 262144, // 0x40000
1085 TRUSTED_FOR_DELEGATION = 524288, // 0x80000
1086 NOT_DELEGATED = 1048576, // 0x100000
1087 USE_DES_KEY_ONLY = 2097152, // 0x200000
1088 DONT_REQUIRE_PREAUTH = 4194304, // 0x400000
1089 PASSWORD_EXPIRED = 8388608, // 0x800000
1090 TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 16777216, // 0x1000000
1091 PARTIAL_SECRETS_ACCOUNT = 67108864 // 0x04000000
1092 }
1093
1094 private static readonly Dictionary<String, String> Replacements = new Dictionary<String, String>()
1095 {
1096 //{System.Environment.NewLine, ""},
1097 //{",", ";"},
1098 {"\"", "'"}
1099 };
1100
1101 public static String CleanString(String StringtoClean)
1102 {
1103 // Remove extra spaces and new lines
1104 String CleanedString = String.Join(" ", ((Convert.ToString(StringtoClean)).Split((string[]) null, StringSplitOptions.RemoveEmptyEntries)));
1105 foreach (String Replacement in Replacements.Keys)
1106 {
1107 CleanedString = CleanedString.Replace(Replacement, Replacements[Replacement]);
1108 }
1109 return CleanedString;
1110 }
1111
1112 public static void UserParser(Object[] AdUsers, DateTime Date1, int PassMaxAge, string FilePath, Dictionary<string, bool> CannotChangePasswordDict, int DormantTimeSpan, int numOfThreads, int flushCnt, String[] OutputType)
1113 {
1114 LDAPClass.Date1 = Date1;
1115 LDAPClass.PassMaxAge = PassMaxAge;
1116 LDAPClass.DormantTimeSpan = DormantTimeSpan;
1117 LDAPClass.FilePath = FilePath;
1118 LDAPClass.CannotChangePasswordDict = CannotChangePasswordDict;
1119
1120 foreach (string OType in OutputType)
1121 {
1122 switch (OType)
1123 {
1124 case "STDOUT":
1125 LDAPClass.ADRSTDOUT = true;
1126 continue;
1127 case "CSV":
1128 LDAPClass.ADRCSV = true;
1129 break;
1130 }
1131 }
1132 if (LDAPClass.ADRCSV == true)
1133 {
1134 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
1135 {
1136 string HeaderRow = String.Format("Name,UserName,Enabled,Cannot Change Password,Password Never Expires,Must Change Password at Logon,Days Since Last Password Change,Password Not Changed after Max Age,Account Locked Out,Never Logged in,Days Since Last Logon,Dormant (> {0} days),Reversibly Encryped Password,Password Not Required,Trusted for Delegation,Trusted to Auth for Delegation,Does Not Require Pre Auth,Logon Workstations,AdminCount,Primary GroupID,SID,SIDHistory,Description,Password LastSet,Last Logon Date,When Created,When Changed,DistinguishedName,CanonicalName",DormantTimeSpan);
1137 file.WriteLine(HeaderRow);
1138 }
1139 }
1140 Console.WriteLine("[*] Total Users: " + AdUsers.Length);
1141 runProcessor(AdUsers, numOfThreads, flushCnt, "Users", "CSV");
1142 }
1143
1144 public static void UserSPNParser(Object[] AdUsers, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
1145 {
1146 LDAPClass.FilePath = FilePath;
1147
1148 foreach (string OType in OutputType)
1149 {
1150 switch (OType)
1151 {
1152 case "STDOUT":
1153 LDAPClass.ADRSTDOUT = true;
1154 continue;
1155 case "CSV":
1156 LDAPClass.ADRCSV = true;
1157 break;
1158 }
1159 }
1160 if (LDAPClass.ADRCSV == true)
1161 {
1162 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
1163 {
1164 string HeaderRow = "Name,Username,Service,Host,Password Last Set,Description";
1165 file.WriteLine(HeaderRow);
1166 }
1167 }
1168 runProcessor(AdUsers, numOfThreads, flushCnt, "UserSPNs", "CSV");
1169 }
1170
1171 public static void GroupParser(Object[] AdGroups, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
1172 {
1173 LDAPClass.FilePath = FilePath;
1174
1175 foreach (string OType in OutputType)
1176 {
1177 switch (OType)
1178 {
1179 case "STDOUT":
1180 LDAPClass.ADRSTDOUT = true;
1181 continue;
1182 case "CSV":
1183 LDAPClass.ADRCSV = true;
1184 break;
1185 }
1186 }
1187 if (LDAPClass.ADRCSV == true)
1188 {
1189 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
1190 {
1191 string HeaderRow = "Group,ManagedBy,whenCreated,whenChanged,Description,SID,DistinguishedName,CanonicalName";
1192 file.WriteLine(HeaderRow);
1193 }
1194 }
1195 Console.WriteLine("[*] Total Groups: " + AdGroups.Length);
1196 runProcessor(AdGroups, numOfThreads, flushCnt, "Groups", "CSV");
1197 }
1198
1199 public static void GroupMemberParser(Object[] AdGroupMembers, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
1200 {
1201 LDAPClass.FilePath = FilePath;
1202
1203 foreach (string OType in OutputType)
1204 {
1205 switch (OType)
1206 {
1207 case "STDOUT":
1208 LDAPClass.ADRSTDOUT = true;
1209 continue;
1210 case "CSV":
1211 LDAPClass.ADRCSV = true;
1212 break;
1213 }
1214 }
1215 if (LDAPClass.ADRCSV == true)
1216 {
1217 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
1218 {
1219 string HeaderRow = "Group Name, Member UserName, Member Name, AccountType";
1220 file.WriteLine(HeaderRow);
1221 }
1222 }
1223 Console.WriteLine("[*] Total GroupMember Objects: " + AdGroupMembers.Length);
1224 runProcessor(AdGroupMembers, numOfThreads, flushCnt, "GroupMembers", "CSV");
1225 }
1226
1227 public static void ComputerParser(Object[] AdComputers, DateTime Date1, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
1228 {
1229 LDAPClass.Date1 = Date1;
1230 LDAPClass.FilePath = FilePath;
1231
1232 foreach (string OType in OutputType)
1233 {
1234 switch (OType)
1235 {
1236 case "STDOUT":
1237 LDAPClass.ADRSTDOUT = true;
1238 continue;
1239 case "CSV":
1240 LDAPClass.ADRCSV = true;
1241 break;
1242 }
1243 }
1244 if (LDAPClass.ADRCSV == true)
1245 {
1246 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
1247 {
1248 string HeaderRow = "Name,DNSHostName,Enabled,OperatingSystem,Days Since Last Logon,Days Since Last Password Change,Trusted for Delegation,Trusted to Auth for Delegation,Username,Primary Group ID,Description,Password LastSet,Last Logon Date,whenCreated,whenChanged,Distinguished Name";
1249 file.WriteLine(HeaderRow);
1250 }
1251 }
1252 Console.WriteLine("[*] Total Computers: " + AdComputers.Length);
1253 runProcessor(AdComputers, numOfThreads, flushCnt, "Computers", "CSV");
1254 }
1255
1256 public static void ComputerSPNParser(Object[] AdComputers, string FilePath, int numOfThreads, int flushCnt, String[] OutputType)
1257 {
1258 LDAPClass.FilePath = FilePath;
1259
1260 foreach (string OType in OutputType)
1261 {
1262 switch (OType)
1263 {
1264 case "STDOUT":
1265 LDAPClass.ADRSTDOUT = true;
1266 continue;
1267 case "CSV":
1268 LDAPClass.ADRCSV = true;
1269 break;
1270 }
1271 }
1272 if (LDAPClass.ADRCSV == true)
1273 {
1274 using (System.IO.StreamWriter file = new System.IO.StreamWriter(@FilePath))
1275 {
1276 string HeaderRow = "Name,Service,Host";
1277 file.WriteLine(HeaderRow);
1278 }
1279 }
1280 runProcessor(AdComputers, numOfThreads, flushCnt, "ComputerSPNs", "CSV");
1281 }
1282
1283 static void runProcessor(Object[] arrayToProcess, int numOfThreads, int flushCnt, string processorType, String resultHandlerType)
1284 {
1285 int totalRecords = arrayToProcess.Length;
1286 //Console.WriteLine(String.Format("Running {0} records over {1} threads, flushing every {2} records",
1287 // totalRecords, numOfThreads, (flushCnt < 0 ? "NEVER" : flushCnt.ToString())));
1288 IRecordProcessor recordProcessor = recordProcessorFactory(processorType);
1289 IResultsHandler resultsHandler = resultHandlerFactory(resultHandlerType, flushCnt);
1290 int numberOfRecordsPerThread = totalRecords / numOfThreads;
1291 int remainders = totalRecords % numOfThreads;
1292
1293 Thread[] threads = new Thread[numOfThreads];
1294 for (int i = 0; i < numOfThreads; i++)
1295 {
1296 int numberOfRecordsToProcess = numberOfRecordsPerThread;
1297 if (i == (numOfThreads - 1))
1298 {
1299 //last thread, do the remaining records
1300 numberOfRecordsToProcess += remainders;
1301 }
1302
1303 //split the full array into chunks to be given to different threads
1304 Object[] sliceToProcess = new Object[numberOfRecordsToProcess];
1305 Array.Copy(arrayToProcess, i * numberOfRecordsPerThread, sliceToProcess, 0, numberOfRecordsToProcess);
1306 ProcessorThread processorThread = new ProcessorThread(i, recordProcessor, resultsHandler, sliceToProcess);
1307 threads[i] = new Thread(processorThread.processThreadRecords);
1308 threads[i].Start();
1309 }
1310 foreach (Thread t in threads)
1311 {
1312 t.Join();
1313 }
1314
1315 resultsHandler.finalise();
1316 }
1317
1318 static IRecordProcessor recordProcessorFactory(String name)
1319 {
1320 switch (name)
1321 {
1322 case "Users":
1323 return new UserRecordProcessor();
1324 case "UserSPNs":
1325 return new UserSPNRecordProcessor();
1326 case "Groups":
1327 return new GroupRecordProcessor();
1328 case "GroupMembers":
1329 return new GroupMemberRecordProcessor();
1330 case "Computers":
1331 return new ComputerRecordProcessor();
1332 case "ComputerSPNs":
1333 return new ComputerSPNRecordProcessor();
1334 }
1335 throw new ArgumentException("Invalid processor type " + name);
1336 }
1337
1338 static IResultsHandler resultHandlerFactory(String name, int flushCnt)
1339 {
1340 switch (name)
1341 {
1342 case "CSV":
1343 return new CsvResultsHandler(flushCnt);
1344 case "TXT":
1345 return new TxtResultsHandler(flushCnt);
1346 }
1347 throw new ArgumentException("Invalid processor type " + name);
1348 }
1349
1350 class ProcessorThread
1351 {
1352 readonly int id;
1353 readonly IRecordProcessor recordProcessor;
1354 readonly IResultsHandler resultsHandler;
1355 readonly Object[] objectsToBeProcessed;
1356
1357 public ProcessorThread(int id, IRecordProcessor recordProcessor, IResultsHandler resultsHandler, Object[] objectsToBeProcessed)
1358 {
1359 this.recordProcessor = recordProcessor;
1360 this.id = id;
1361 this.resultsHandler = resultsHandler;
1362 this.objectsToBeProcessed = objectsToBeProcessed;
1363 }
1364
1365 public void processThreadRecords()
1366 {
1367 for (int i = 0; i < objectsToBeProcessed.Length; i++)
1368 {
1369 Object[] result = recordProcessor.processRecord(objectsToBeProcessed[i]);
1370 resultsHandler.processResults(result); //this is a thread safe operation
1371 }
1372 }
1373 }
1374
1375 //The interface and implmentation class used to process a record (this implemmentation just returns a log type string)
1376
1377 interface IRecordProcessor
1378 {
1379 Object[] processRecord(Object record);
1380 }
1381
1382 class UserRecordProcessor : IRecordProcessor
1383 {
1384 public Object[] processRecord(Object record)
1385 {
1386 try
1387 {
1388 SearchResult AdUser = (SearchResult) record;
1389 bool? Enabled = null;
1390 bool? PasswordNeverExpires = null;
1391 bool? AccountLockedOut = null;
1392 bool? ReversiblyEncrypedPassword = null;
1393 bool? PasswordNotRequired = null;
1394 bool? TrustedforDelegation = null;
1395 bool? TrustedtoAuthforDelegation = null;
1396 bool? DoesNotRequirePreAuth = null;
1397 // When the user is not allowed to query the UserAccountControl attribute.
1398 if (AdUser.Properties["useraccountcontrol"].Count != 0)
1399 {
1400 var userFlags = (UACFlags) AdUser.Properties["useraccountcontrol"][0];
1401 Enabled = !((userFlags & UACFlags.ACCOUNTDISABLE) == UACFlags.ACCOUNTDISABLE);
1402 PasswordNeverExpires = (userFlags & UACFlags.DONT_EXPIRE_PASSWD) == UACFlags.DONT_EXPIRE_PASSWD;
1403 AccountLockedOut = (userFlags & UACFlags.LOCKOUT) == UACFlags.LOCKOUT;
1404 ReversiblyEncrypedPassword = (userFlags & UACFlags.ENCRYPTED_TEXT_PASSWORD_ALLOWED) == UACFlags.ENCRYPTED_TEXT_PASSWORD_ALLOWED;
1405 PasswordNotRequired = (userFlags & UACFlags.PASSWD_NOTREQD) == UACFlags.PASSWD_NOTREQD;
1406 TrustedforDelegation = (userFlags & UACFlags.TRUSTED_FOR_DELEGATION) == UACFlags.TRUSTED_FOR_DELEGATION;
1407 TrustedtoAuthforDelegation = (userFlags & UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) == UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION;
1408 DoesNotRequirePreAuth = (userFlags & UACFlags.DONT_REQUIRE_PREAUTH) == UACFlags.DONT_REQUIRE_PREAUTH;
1409 }
1410 bool MustChangePasswordatLogon = false;
1411 int DaysSinceLastPasswordChange = -1;
1412 bool PasswordNotChangedafterMaxAge = false;
1413 bool NeverLoggedIn = false;
1414 int DaysSinceLastLogon = -1;
1415 bool Dormant = false;
1416 DateTime PasswordLastSet = new DateTime();
1417 DateTime LastLogonDate = new DateTime();
1418 bool CannotChangePassword = CannotChangePasswordDict[Convert.ToString(AdUser.Properties["samaccountname"][0])];
1419 if (AdUser.Properties["pwdLastSet"].Count != 0)
1420 {
1421 if (Convert.ToString(AdUser.Properties["pwdlastset"][0]) == "0")
1422 {
1423 MustChangePasswordatLogon = true;
1424 }
1425 else
1426 {
1427 PasswordLastSet = DateTime.FromFileTime((long)(AdUser.Properties["pwdLastSet"][0]));
1428 DaysSinceLastPasswordChange = Math.Abs((Date1 - PasswordLastSet).Days);
1429 if (DaysSinceLastPasswordChange > PassMaxAge)
1430 {
1431 PasswordNotChangedafterMaxAge = true;
1432 }
1433 }
1434 }
1435 if (AdUser.Properties["lastlogontimestamp"].Count != 0)
1436 {
1437 LastLogonDate = DateTime.FromFileTime((long)(AdUser.Properties["lastlogontimestamp"][0]));
1438 DaysSinceLastLogon = Math.Abs((Date1 - LastLogonDate).Days);
1439 if (DaysSinceLastLogon > DormantTimeSpan)
1440 {
1441 Dormant = true;
1442 }
1443 }
1444 else
1445 {
1446 NeverLoggedIn = true;
1447 }
1448 string SIDHistory = "";
1449 if (AdUser.Properties["sidhistory"].Count >= 1)
1450 {
1451 string sids = "";
1452 for (int i = 0; i < AdUser.Properties["sidhistory"].Count; i++)
1453 {
1454 var history = AdUser.Properties["sidhistory"][i];
1455 sids = sids + "," + Convert.ToString(new SecurityIdentifier((byte[])history, 0));
1456 }
1457 SIDHistory = sids.TrimStart(',');
1458 }
1459 if (LDAPClass.ADRSTDOUT == true)
1460 {
1461 Console.WriteLine("\nName{0,33}{2}\nUserName{0,29}{3}\nEnabled{0,30}{4}\nCannot Change Password{0,15}{5}\nPassword Never Expires{0,15}{6}\nMust Change Password at Logon{0,8}{7}\nDays Since Last Password Change{0,6}{8}\nPassword Not Changed after Max Age{0}{9}\nAccount Locked Out{0,19}{10}\nNever Logged in{0,22}{11}\nDays Since Last Logon{0,16}{12}\nDormant (> {1} days){0,18}{13}\nReversibly Encryped Password{0,9}{14}\nPassword Not Required{0,16}{15}\nTrusted for Delegation{0,15}{16}\nTrusted to Auth for Delegation{0,7}{17}\nDoes Not Require Pre Auth{0,12}{18}\nLogon Workstations{0,19}{19}\nAdminCount{0,27}{20}\nPrimary GroupID{0,22}{21}\nSID{0,34}{22}\nSIDHistory{0,27}{23}\nDescription{0,26}{24}\nPassword LastSet{0,21}{25}\nLast Logon Date{0,22}{26}\nWhen Created{0,25}{27}\nWhen Changed{0,25}{28}\nDistinguishedName{0,20}{29}\nCanonicalName{0,24}{30}\n", " : ", DormantTimeSpan, (AdUser.Properties["name"].Count != 0 ? AdUser.Properties["name"][0] : ""), (AdUser.Properties["samaccountname"].Count != 0 ? AdUser.Properties["samaccountname"][0] : ""), Enabled, CannotChangePassword, PasswordNeverExpires, MustChangePasswordatLogon, DaysSinceLastPasswordChange, PasswordNotChangedafterMaxAge, AccountLockedOut, NeverLoggedIn, DaysSinceLastLogon, Dormant, ReversiblyEncrypedPassword, PasswordNotRequired, TrustedforDelegation, TrustedtoAuthforDelegation, DoesNotRequirePreAuth, (AdUser.Properties["userworkstations"].Count != 0 ? AdUser.Properties["userworkstations"][0] : ""), (AdUser.Properties["admincount"].Count != 0 ? AdUser.Properties["admincount"][0] : ""), (AdUser.Properties["primarygroupid"].Count != 0 ? AdUser.Properties["primarygroupid"][0] : ""), Convert.ToString(new SecurityIdentifier((byte[])AdUser.Properties["objectSID"][0], 0)), SIDHistory, (AdUser.Properties["Description"].Count != 0 ? AdUser.Properties["Description"][0] : ""), PasswordLastSet, LastLogonDate, (AdUser.Properties["whencreated"].Count != 0 ? AdUser.Properties["whencreated"][0] : ""), (AdUser.Properties["whenchanged"].Count != 0 ? AdUser.Properties["whenchanged"][0] : ""), (AdUser.Properties["distinguishedname"].Count != 0 ? AdUser.Properties["distinguishedname"][0] : ""), (AdUser.Properties["canonicalname"].Count != 0 ? AdUser.Properties["canonicalname"][0] : ""));
1462 }
1463 return new Object[] { (AdUser.Properties["name"].Count != 0 ? AdUser.Properties["name"][0] : ""), (AdUser.Properties["samaccountname"].Count != 0 ? AdUser.Properties["samaccountname"][0] : ""), Enabled, CannotChangePassword, PasswordNeverExpires, MustChangePasswordatLogon, DaysSinceLastPasswordChange, PasswordNotChangedafterMaxAge, AccountLockedOut, NeverLoggedIn, DaysSinceLastLogon, Dormant, ReversiblyEncrypedPassword, PasswordNotRequired, TrustedforDelegation, TrustedtoAuthforDelegation, DoesNotRequirePreAuth, (AdUser.Properties["userworkstations"].Count != 0 ? AdUser.Properties["userworkstations"][0] : ""), (AdUser.Properties["admincount"].Count != 0 ? AdUser.Properties["admincount"][0] : ""), (AdUser.Properties["primarygroupid"].Count != 0 ? AdUser.Properties["primarygroupid"][0] : ""), Convert.ToString(new SecurityIdentifier((byte[])AdUser.Properties["objectSID"][0], 0)), SIDHistory, (AdUser.Properties["Description"].Count != 0 ? AdUser.Properties["Description"][0] : ""), PasswordLastSet, LastLogonDate, (AdUser.Properties["whencreated"].Count != 0 ? AdUser.Properties["whencreated"][0] : ""), (AdUser.Properties["whenchanged"].Count != 0 ? AdUser.Properties["whenchanged"][0] : ""), (AdUser.Properties["distinguishedname"].Count != 0 ? AdUser.Properties["distinguishedname"][0] : ""), (AdUser.Properties["canonicalname"].Count != 0 ? AdUser.Properties["canonicalname"][0] : "") };
1464 }
1465 catch (Exception e)
1466 {
1467 Console.WriteLine("{0} Exception caught.", e);
1468 return new Object[] { };
1469 }
1470 }
1471 }
1472
1473 class UserSPNRecordProcessor : IRecordProcessor
1474 {
1475 public Object[] processRecord(Object record)
1476 {
1477 try
1478 {
1479 SearchResult AdUser = (SearchResult) record;
1480 List<Object> SPNList = new List<Object>();
1481 DateTime PasswordLastSet = DateTime.FromFileTime((long)(AdUser.Properties["pwdLastSet"][0]));
1482 String Description = (AdUser.Properties["Description"].Count != 0 ? Convert.ToString(AdUser.Properties["Description"][0]) : "");
1483 foreach (String SPN in AdUser.Properties["serviceprincipalname"])
1484 {
1485 String[] SPNArray = SPN.Split('/');
1486 SPNList.Add(new Object[] { AdUser.Properties["name"][0], AdUser.Properties["samaccountname"][0], SPNArray[0], SPNArray[1], PasswordLastSet, Description });
1487 }
1488 if (LDAPClass.ADRSTDOUT == true)
1489 {
1490 foreach (Object[] Obj in SPNList.ToArray())
1491 {
1492 Console.WriteLine("Name{0,16}{1}\nUsername{0,12}{2}\nService{0,13}{3}\nHost{0,16}{4}\nPassword Last Set{0}{5}\nDescription{0,9}{6}\n", " : ", Obj[0], Obj[1], Obj[2], Obj[3], Obj[4], Obj[5]);
1493 }
1494 }
1495 return SPNList.ToArray();
1496 }
1497 catch (Exception e)
1498 {
1499 Console.WriteLine("{0} Exception caught.", e);
1500 return new Object[] { };
1501 }
1502 }
1503 }
1504
1505 class GroupRecordProcessor : IRecordProcessor
1506 {
1507 public Object[] processRecord(Object record)
1508 {
1509 try
1510 {
1511
1512 SearchResult AdGroup = (SearchResult) record;
1513 string ManagedByValue = AdGroup.Properties["managedby"].Count != 0 ? Convert.ToString(AdGroup.Properties["managedby"][0]) : "";
1514 string ManagedBy = "";
1515 if (AdGroup.Properties["managedBy"].Count != 0)
1516 {
1517 ManagedBy = (ManagedByValue.Split(',')[0]).Split('=')[1];
1518 }
1519 if (LDAPClass.ADRSTDOUT == true)
1520 {
1521 Console.WriteLine("Group{0,15}{1}\nManagedBy{0,11}{2}\nwhenCreated{0,9}{3}\nwhenChanged{0,9}{4}\nDescription{0,9}{5}\nSID{0,17}{6}\nDistinguishedName{0}{7}\nCanonicalName{0,7}{8}\n", " : ", AdGroup.Properties["samaccountname"][0], ManagedBy, AdGroup.Properties["whencreated"][0], AdGroup.Properties["whenchanged"][0], (AdGroup.Properties["Description"].Count != 0 ? AdGroup.Properties["Description"][0] : ""), Convert.ToString(new SecurityIdentifier((byte[])AdGroup.Properties["objectSID"][0], 0)), AdGroup.Properties["distinguishedname"][0], AdGroup.Properties["canonicalname"][0]);
1522 }
1523 return new Object[] { AdGroup.Properties["samaccountname"][0], ManagedBy, AdGroup.Properties["whencreated"][0], AdGroup.Properties["whenchanged"][0], (AdGroup.Properties["Description"].Count != 0 ? AdGroup.Properties["Description"][0] : ""), Convert.ToString(new SecurityIdentifier((byte[])AdGroup.Properties["objectSID"][0], 0)), AdGroup.Properties["distinguishedname"][0], AdGroup.Properties["canonicalname"][0] };
1524 }
1525 catch (Exception e)
1526 {
1527 Console.WriteLine("{0} Exception caught.", e);
1528 return new Object[] { };
1529 }
1530 }
1531 }
1532
1533 class GroupMemberRecordProcessor : IRecordProcessor
1534 {
1535 public Object[] processRecord(Object record)
1536 {
1537 try
1538 {
1539 // https://github.com/BloodHoundAD/BloodHound/blob/master/PowerShell/BloodHound.ps1
1540 SearchResult AdGroup = (SearchResult) record;
1541 List<Object> GroupsList = new List<Object>();
1542 string SamAccountType = AdGroup.Properties["samaccounttype"].Count != 0 ? Convert.ToString(AdGroup.Properties["samaccounttype"][0]) : "";
1543 string AccountType = "";
1544 string GroupName = "";
1545 string MemberUserName = "-";
1546 string MemberName = "";
1547 if (Groups.Contains(SamAccountType))
1548 {
1549 AccountType = "group";
1550 MemberName = ((Convert.ToString(AdGroup.Properties["DistinguishedName"][0])).Split(',')[0]).Split('=')[1];
1551 foreach (String GroupMember in AdGroup.Properties["memberof"])
1552 {
1553 GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
1554 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
1555 }
1556 }
1557 if (Users.Contains(SamAccountType))
1558 {
1559 AccountType = "user";
1560 MemberName = ((Convert.ToString(AdGroup.Properties["DistinguishedName"][0])).Split(',')[0]).Split('=')[1];
1561 MemberUserName = Convert.ToString(AdGroup.Properties["sAMAccountName"][0]);
1562 foreach (String GroupMember in AdGroup.Properties["memberof"])
1563 {
1564 GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
1565 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
1566 }
1567 }
1568 if (Computers.Contains(SamAccountType))
1569 {
1570 AccountType = "computer";
1571 MemberName = ((Convert.ToString(AdGroup.Properties["DistinguishedName"][0])).Split(',')[0]).Split('=')[1];
1572 MemberUserName = Convert.ToString(AdGroup.Properties["sAMAccountName"][0]);
1573 foreach (String GroupMember in AdGroup.Properties["memberof"])
1574 {
1575 GroupName = ((Convert.ToString(GroupMember)).Split(',')[0]).Split('=')[1];
1576 GroupsList.Add(new Object[] { GroupName, MemberUserName, MemberName, AccountType });
1577 }
1578 }
1579 if (TrustAccounts.Contains(SamAccountType))
1580 {
1581 // TO DO
1582 }
1583 if (LDAPClass.ADRSTDOUT == true)
1584 {
1585 foreach (Object[] Obj in GroupsList.ToArray())
1586 {
1587 Console.WriteLine("Group Name{0,8}{1}\nMember UserName{0}{2}\nMember Name{0,7}{3}\nAccountType{0,7}{4}\n", " : ", Obj[0], Obj[1], Obj[2], Obj[3]);
1588 }
1589 }
1590 return GroupsList.ToArray();
1591 }
1592 catch (Exception e)
1593 {
1594 Console.WriteLine(((SearchResult)record).Properties["DistinguishedName"][0]);
1595 Console.WriteLine("{0} Exception caught.", e);
1596 return new Object[] { };
1597 }
1598 }
1599 }
1600
1601 class ComputerRecordProcessor : IRecordProcessor
1602 {
1603 public Object[] processRecord(Object record)
1604 {
1605 try
1606 {
1607 SearchResult AdComputer = (SearchResult) record;
1608 bool? Enabled = null;
1609 bool? TrustedforDelegation = null;
1610 bool? TrustedtoAuthforDelegation = null;
1611 // When the user is not allowed to query the UserAccountControl attribute.
1612 if (AdComputer.Properties["useraccountcontrol"].Count != 0)
1613 {
1614 var userFlags = (UACFlags) AdComputer.Properties["useraccountcontrol"][0];
1615 Enabled = !((userFlags & UACFlags.ACCOUNTDISABLE) == UACFlags.ACCOUNTDISABLE);
1616 TrustedforDelegation = (userFlags & UACFlags.TRUSTED_FOR_DELEGATION) == UACFlags.TRUSTED_FOR_DELEGATION;
1617 TrustedtoAuthforDelegation = (userFlags & UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) == UACFlags.TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION;
1618 }
1619 int DaysSinceLastPasswordChange = -1;
1620 int DaysSinceLastLogon = -1;
1621 DateTime LastLogonDate = new DateTime();
1622 if (AdComputer.Properties["lastlogontimestamp"].Count != 0)
1623 {
1624 LastLogonDate = DateTime.FromFileTime((long)(AdComputer.Properties["lastlogontimestamp"][0]));
1625 DaysSinceLastLogon = Math.Abs((Date1 - LastLogonDate).Days);
1626 if (DaysSinceLastLogon >= 152246)
1627 {
1628 DaysSinceLastLogon = -1;
1629 }
1630 }
1631 DateTime PasswordLastSet = DateTime.FromFileTime((long)(AdComputer.Properties["pwdLastSet"][0]));
1632 if (AdComputer.Properties["pwdLastSet"].Count != 0)
1633 {
1634 DaysSinceLastPasswordChange = Math.Abs((Date1 - PasswordLastSet).Days);
1635 if (DaysSinceLastPasswordChange >= 152246)
1636 {
1637 DaysSinceLastPasswordChange = -1;
1638 }
1639 }
1640 if (LDAPClass.ADRSTDOUT == true)
1641 {
1642 Console.WriteLine("Name{0,30}{1}\nDNSHostName{0,23}{2}\nEnabled{0,27}{3}\nOperatingSystem{0,19}{4}\nDays Since Last Logon{0,13}{5}\nDays Since Last Password Change{0}{6}\nTrusted for Delegation{0,12}{7}\nTrusted to Auth for Delegation{0,4}{8}\nUsername{0,26}{9}\nPrimary Group ID{0,18}{10}\nDescription{0,23}{11}\nPassword LastSet{0,18}{12}\nLast Logon Date{0,19}{13}\nwhenCreated{0,23}{14}\nwhenChanged{0,23}{15}\nDistinguished Name{0,16}{16}\n", " : ", (AdComputer.Properties["name"].Count != 0 ? AdComputer.Properties["name"][0] : ""), (AdComputer.Properties["dnshostname"].Count != 0 ? AdComputer.Properties["dnshostname"][0] : ""), Enabled, (AdComputer.Properties["operatingsystem"].Count != 0 ? AdComputer.Properties["operatingsystem"][0] : "-"), DaysSinceLastLogon, DaysSinceLastPasswordChange, TrustedforDelegation, TrustedtoAuthforDelegation, (AdComputer.Properties["samaccountname"].Count != 0 ? AdComputer.Properties["samaccountname"][0] : ""), (AdComputer.Properties["primarygroupid"].Count != 0 ? AdComputer.Properties["primarygroupid"][0] : ""), (AdComputer.Properties["Description"].Count != 0 ? AdComputer.Properties["Description"][0] : ""), PasswordLastSet, LastLogonDate, AdComputer.Properties["whencreated"][0], AdComputer.Properties["whenchanged"][0], AdComputer.Properties["distinguishedname"][0]);
1643 }
1644 return new Object[] { (AdComputer.Properties["name"].Count != 0 ? AdComputer.Properties["name"][0] : ""), (AdComputer.Properties["dnshostname"].Count != 0 ? AdComputer.Properties["dnshostname"][0] : ""), Enabled, (AdComputer.Properties["operatingsystem"].Count != 0 ? AdComputer.Properties["operatingsystem"][0] : "-"), DaysSinceLastLogon, DaysSinceLastPasswordChange, TrustedforDelegation, TrustedtoAuthforDelegation, (AdComputer.Properties["samaccountname"].Count != 0 ? AdComputer.Properties["samaccountname"][0] : ""), (AdComputer.Properties["primarygroupid"].Count != 0 ? AdComputer.Properties["primarygroupid"][0] : ""), (AdComputer.Properties["Description"].Count != 0 ? AdComputer.Properties["Description"][0] : ""), PasswordLastSet, LastLogonDate, AdComputer.Properties["whencreated"][0], AdComputer.Properties["whenchanged"][0], AdComputer.Properties["distinguishedname"][0] };
1645 }
1646 catch (Exception e)
1647 {
1648 Console.WriteLine(((SearchResult)record).Properties["name"][0]);
1649 Console.WriteLine("{0} Exception caught.", e);
1650 return new Object[] { };
1651 }
1652 }
1653 }
1654
1655 class ComputerSPNRecordProcessor : IRecordProcessor
1656 {
1657 public Object[] processRecord(Object record)
1658 {
1659 try
1660 {
1661 SearchResult AdComputer = (SearchResult) record;
1662 List<Object> SPNList = new List<Object>();
1663 foreach (String SPN in AdComputer.Properties["serviceprincipalname"])
1664 {
1665 String[] SPNArray = SPN.Split('/');
1666 SPNList.Add(new Object[] { AdComputer.Properties["name"][0], SPNArray[0], SPNArray[1] });
1667 }
1668 if (LDAPClass.ADRSTDOUT == true)
1669 {
1670 foreach (Object[] Obj in SPNList.ToArray())
1671 {
1672 Console.WriteLine("Name{0,6}{1}\nService{0}{2}\nHost{0,6}{3}\n", " : ", Obj[0], Obj[1], Obj[2]);
1673 }
1674 }
1675 return SPNList.ToArray();
1676 }
1677 catch (Exception e)
1678 {
1679 Console.WriteLine("{0} Exception caught.", e);
1680 return new Object[] { };
1681 }
1682 }
1683 }
1684
1685 //The interface and implmentation class used to handle the results (this implementation just writes the strings to a file)
1686
1687 interface IResultsHandler
1688 {
1689 void processResults(Object[] t);
1690
1691 void finalise();
1692 }
1693
1694 abstract class SimpleResultsHandler : IResultsHandler
1695 {
1696 private Object lockObj = new Object();
1697 private List<String> processed = new List<String>();
1698 private readonly int flushCnt;
1699
1700 public SimpleResultsHandler(int flushCnt)
1701 {
1702 this.flushCnt = flushCnt;
1703 }
1704
1705 public void processResults(Object[] results)
1706 {
1707 lock (lockObj)
1708 {
1709 if (results.Length != 0)
1710 {
1711 if (results[0] is System.Object[])
1712 {
1713 for (var i = 0; i < results.Length; i++)
1714 {
1715 processed.Add(convertObject((Object[])results[i]));
1716 }
1717 }
1718 else
1719 {
1720 processed.Add(convertObject(results));
1721 }
1722 if (flushCnt > 0)
1723 {
1724 if (processed.Count >= flushCnt)
1725 {
1726 writeFile();
1727 }
1728 }
1729 }
1730 }
1731 }
1732
1733 public void finalise()
1734 {
1735 writeFile();
1736 }
1737
1738 private void writeFile()
1739 {
1740 lock (lockObj)
1741 {
1742 if (LDAPClass.ADRCSV == true)
1743 {
1744 using (StreamWriter outputFile = new StreamWriter(@LDAPClass.FilePath, true))
1745 {
1746 outputFile.Write(String.Join("\r\n", processed.ToArray()));
1747 }
1748 }
1749 processed.Clear();
1750 }
1751 }
1752
1753 protected abstract String convertObject(Object[] resultsObject);
1754 }
1755
1756
1757 class CsvResultsHandler : SimpleResultsHandler
1758 {
1759 public CsvResultsHandler(int flushCnt) : base(flushCnt)
1760 {
1761 }
1762
1763 protected override String convertObject(Object[] resultsObject)
1764 {
1765 return createCsvLine(resultsObject);
1766 }
1767
1768 static String createCsvLine(Object[] resultsObject)
1769 {
1770 try
1771 {
1772 // No String.Join(String, Object[]) in CLR 2.0.50727 (Windows 7)
1773 String[] row = new String[resultsObject.Length];
1774 for (int i=0; i < resultsObject.Length; i++)
1775 {
1776 //String StringtoClean = Regex.Replace(Convert.ToString(resultsObject[i]),@"[^\S ]+", "");
1777 row[i] = CleanString(Convert.ToString(resultsObject[i]));
1778 }
1779 return "\"" + String.Join("\",\"", row) + "\"";
1780 }
1781 catch (Exception e)
1782 {
1783 Console.WriteLine("{0} Exception caught.", e);
1784 return "";
1785 }
1786 }
1787 }
1788
1789 class TxtResultsHandler : SimpleResultsHandler
1790 {
1791 public TxtResultsHandler(int flushCnt) : base(flushCnt)
1792 {
1793 }
1794
1795 protected override String convertObject(Object[] resultsObject)
1796 {
1797 try
1798 {
1799 // No String.Join(String, Object[]) in CLR 2.0.50727 (Windows 7)
1800 String[] row = new String[resultsObject.Length];
1801 for (int i=0; i < resultsObject.Length; i++)
1802 {
1803 row[i] = CleanString(Convert.ToString(resultsObject[i]));
1804 }
1805 return String.Join("\t", row);
1806 }
1807 catch (Exception e)
1808 {
1809 Console.WriteLine("{0} Exception caught.", e);
1810 return "";
1811 }
1812 }
1813 }
1814 }
1815}
1816"@
1817
1818#Add-Type -TypeDefinition $Source -ReferencedAssemblies ([System.String[]]@(([system.reflection.assembly]::LoadWithPartialName("Microsoft.ActiveDirectory.Management")).Location,([system.reflection.assembly]::LoadWithPartialName("System.DirectoryServices")).Location))
1819
1820Function Get-DateDiff
1821{
1822<#
1823.SYNOPSIS
1824 Get difference between two dates.
1825
1826.DESCRIPTION
1827 Returns the difference between two dates.
1828
1829.PARAMETER Date1
1830 [DateTime]
1831 Date
1832
1833.PARAMETER Date2
1834 [DateTime]
1835 Date
1836
1837.OUTPUTS
1838 [System.ValueType.TimeSpan]
1839 Returns the difference between the two dates.
1840#>
1841 param (
1842 [Parameter(Mandatory = $true)]
1843 [DateTime] $Date1,
1844
1845 [Parameter(Mandatory = $true)]
1846 [DateTime] $Date2
1847 )
1848 if ($Date2 -gt $Date1)
1849 {
1850 $DDiff = $Date2 - $Date1
1851 }
1852 Else
1853 {
1854 $DDiff = $Date1 - $Date2
1855 }
1856 Return $DDiff
1857}
1858
1859Function Get-DNtoFQDN
1860{
1861<#
1862.SYNOPSIS
1863 Gets Domain Distinguished Name (DN) from the Fully Qualified Domain Name (FQDN).
1864
1865.DESCRIPTION
1866 Converts Domain Distinguished Name (DN) to Fully Qualified Domain Name (FQDN).
1867
1868.PARAMETER ADObjectDN
1869 [string]
1870 Domain Distinguished Name (DN)
1871
1872.OUTPUTS
1873 [String]
1874 Returns the Fully Qualified Domain Name (FQDN).
1875
1876.LINK
1877 https://adsecurity.org/?p=440
1878#>
1879 param(
1880 [Parameter(Mandatory = $true)]
1881 [string] $ADObjectDN
1882 )
1883 # Modified version from https://adsecurity.org/?p=440
1884 [array] $ADObjectDNArray = $ADObjectDN -Split ("DC=")
1885 $ADObjectDNArray | ForEach-Object {
1886 [array] $temp = $_ -Split (",")
1887 [string] $ADObjectDNArrayItemDomainName += $temp[0] + "."
1888 }
1889 $ADObjectDNDomainName = $ADObjectDNArrayItemDomainName.Substring(1, $ADObjectDNArrayItemDomainName.Length - 2)
1890 Return $ADObjectDNDomainName
1891}
1892
1893Function Get-ADRExcelComObj
1894{
1895<#
1896.SYNOPSIS
1897 Creates a ComObject to interact with Microsoft Excel.
1898
1899.DESCRIPTION
1900 Creates a ComObject to interact with Microsoft Excel if installed, else warning is raised.
1901
1902.OUTPUTS
1903 [System.__ComObject] and [System.MarshalByRefObject]
1904 Creates global variables $excel and $workbook.
1905#>
1906 #Check if Excel is installed.
1907 Try
1908 {
1909 $global:excel = New-Object -ComObject excel.application
1910 }
1911 Catch
1912 {
1913 Write-Warning "[*] Excel is not installed. Skipping ADRecon-Report.xlsx. Use the -GenExcel parameter to generate the ADRecon-Report.xslx on a host with Microsoft Excel installed."
1914 Write-Output "Run Get-Help .\ADRecon.ps1 -Examples for additional information."
1915 return $null
1916 }
1917 $excel.visible = $true
1918 $global:workbook = $excel.Workbooks.Add()
1919 If ($workbook.Worksheets.Count -eq 3)
1920 {
1921 $workbook.WorkSheets.Item(3).Delete()
1922 $workbook.WorkSheets.Item(2).Delete()
1923 }
1924}
1925
1926Function Get-ADRExcelWorkbook
1927{
1928<#
1929.SYNOPSIS
1930 Adds a WorkSheet to the Workbook.
1931
1932.DESCRIPTION
1933 Adds a WorkSheet to the Workbook using the $workboook global variable and assigns it a name.
1934
1935.PARAMETER name
1936 [string]
1937 Name of the WorkSheet.
1938#>
1939 param (
1940 [Parameter(Mandatory = $true)]
1941 [string] $name
1942 )
1943 $workbook.Worksheets.Add() | Out-Null
1944 $worksheet = $workbook.Worksheets.Item(1)
1945 $worksheet.Name = $name
1946}
1947
1948Function Get-ADRExcelImport
1949{
1950<#
1951.SYNOPSIS
1952 Helper to import CSV to the current WorkSheet.
1953
1954.DESCRIPTION
1955 Helper to import CSV to the current WorkSheet. Supports two methods.
1956
1957.PARAMETER filename
1958 [string]
1959 Filename of the CSV file to import.
1960
1961.PARAMETER method
1962 [int]
1963 Method to use for the import.
1964#>
1965 param (
1966 [Parameter(Mandatory = $true)]
1967 [string] $filename,
1968
1969 [Parameter(Mandatory = $true)]
1970 [int] $method
1971 )
1972 If ($method -eq 1)
1973 {
1974 $row = 1
1975 $column = 1
1976 $worksheet = $workbook.Worksheets.Item(1)
1977 If (Test-Path $filename)
1978 {
1979 $ADTemp = Import-Csv -Path $filename
1980 $ADTemp | ForEach-Object {
1981 Foreach ($prop in $_.PSObject.Properties)
1982 {
1983 $worksheet.Cells.Item($row, $column) = $prop.Name
1984 $worksheet.Cells.Item($row, $column + 1) = $prop.Value
1985 $row++
1986 }
1987 }
1988 Remove-Variable ADTemp
1989 $listObject = $worksheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheet.UsedRange, $null, [Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes, $null)
1990 $listObject.TableStyle = "TableStyleLight2" # Style Cheat Sheet: https://msdn.microsoft.com/en-au/library/documentformat.openxml.spreadsheet.tablestyle.aspx
1991 $usedRange = $worksheet.UsedRange
1992 $usedRange.EntireColumn.AutoFit() | Out-Null
1993 }
1994 Else
1995 {
1996 $worksheet.Cells.Item($row, $column) = "Error!"
1997 }
1998 Remove-Variable filename
1999 }
2000 Elseif ($method -eq 2)
2001 {
2002 If (Test-Path $filename)
2003 {
2004 $worksheet = $workbook.Worksheets.Item(1)
2005 $TxtConnector = ("TEXT;" + $filename)
2006 $CellRef = $worksheet.Range("A1")
2007 #Build, use and remove the text file connector
2008 $Connector = $worksheet.QueryTables.add($TxtConnector, $CellRef)
2009
2010 #65001: Unicode (UTF-8)
2011 $worksheet.QueryTables.item($Connector.name).TextFilePlatform = 65001
2012 $worksheet.QueryTables.item($Connector.name).TextFileCommaDelimiter = $True
2013 $worksheet.QueryTables.item($Connector.name).TextFileParseType = 1
2014 $worksheet.QueryTables.item($Connector.name).Refresh() | Out-Null
2015 $worksheet.QueryTables.item($Connector.name).delete()
2016 $listObject = $worksheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheet.UsedRange, $null, [Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes, $null)
2017 $listObject.TableStyle = "TableStyleLight2" # Style Cheat Sheet: https://msdn.microsoft.com/en-au/library/documentformat.openxml.spreadsheet.tablestyle.aspx
2018 $worksheet.UsedRange.EntireColumn.AutoFit() | Out-Null
2019 }
2020 Remove-Variable filename
2021 }
2022}
2023
2024Function Get-ADRExcelChart
2025{
2026<#
2027.SYNOPSIS
2028 Helper to add charts to the current WorkSheet.
2029
2030.DESCRIPTION
2031 Helper to add charts to the current WorkSheet.
2032
2033.PARAMETER ChartType
2034 [int]
2035 Chart Type.
2036
2037.PARAMETER ChartLayout
2038 [int]
2039 Chart Layout.
2040
2041.PARAMETER ChartTitle
2042 [string]
2043 Title of the Chart.
2044
2045.PARAMETER RangetoCover
2046 WorkSheet Range to be covered by the Chart.
2047
2048.PARAMETER chartdata
2049 Data for the Chart.
2050#>
2051 param (
2052 [Parameter(Mandatory = $true)]
2053 [int] $ChartType,
2054
2055 [Parameter(Mandatory = $true)]
2056 [int] $ChartLayout,
2057
2058 [Parameter(Mandatory = $true)]
2059 [string] $ChartTitle,
2060
2061 [Parameter(Mandatory = $true)]
2062 $RangetoCover,
2063
2064 [Parameter(Mandatory = $false)]
2065 $chartdata
2066 )
2067 $worksheet = $workbook.Worksheets.Item(1)
2068 $xlChart=[Microsoft.Office.Interop.Excel.XLChartType]
2069 $chart=$worksheet.Shapes.AddChart().Chart
2070 $chart.chartType= $ChartType
2071 $chart.ApplyLayout($ChartLayout)
2072 $xlDirection=[Microsoft.Office.Interop.Excel.XLDirection]
2073 If ($null -eq $chartdata)
2074 {
2075 $start=$worksheet.range("A1")
2076 #get the last cell
2077 $Y=$worksheet.Range($start,$start.End($xlDirection::xlDown))
2078 $start=$worksheet.range("B1")
2079 #get the last cell
2080 $X=$worksheet.Range($start,$start.End($xlDirection::xlDown))
2081 $chartdata=$worksheet.Range("A$($Y.item(1).Row):A$($Y.item($Y.count).Row),B$($X.item(1).Row):B$($X.item($X.count).Row)")
2082 }
2083 $chart.SetSourceData($chartdata)
2084 $chart.seriesCollection(1).Select() | Out-Null
2085 $chart.SeriesCollection(1).ApplyDataLabels() | out-Null
2086 #modify the chart title
2087 $chart.HasTitle = $True
2088 $chart.ChartTitle.Text = $ChartTitle
2089 If ($ChartTitle -eq "Status of User Accounts")
2090 {
2091 $chart.PlotBy = 1
2092 $chart.axes(2).axistitle.text = "Count"
2093 }
2094 #Reposition the Chart
2095 $temp = $worksheet.Range($RangetoCover)
2096 $chartparent = $chart.parent
2097 # $chartparent.placement = 3
2098 $chartparent.top = $temp.Top
2099 $chartparent.left = $temp.Left
2100 $chartparent.width = $temp.Width
2101 If ($ChartTitle -ne "Privileged Groups in AD")
2102 {
2103 $chartparent.height = $temp.Height
2104 }
2105 #$chart.Legend.Delete()
2106}
2107
2108Function Get-ADRGenExcel
2109{
2110<#
2111.SYNOPSIS
2112 Automates the generation of the ADRecon report.
2113
2114.DESCRIPTION
2115 Automates the generation of the ADRecon report. If specific files exist, they are imported into the ADRecon report.
2116
2117.PARAMETER ExcelPath
2118 [string]
2119 Path for ADRecon output folder containing the CSV files to generate the ADRecon-Report.xlsx
2120
2121.OUTPUTS
2122 Creates the ADRecon-Report.xlsx report in the folder.
2123#>
2124 param(
2125 [Parameter(Mandatory = $true)]
2126 [string] $ExcelPath
2127 )
2128 $ExcelPath = $((Convert-Path $ExcelPath).TrimEnd("\"))
2129 $ReportPath = -join($ExcelPath,'\','CSV-Files')
2130 If (!(Test-Path $ReportPath))
2131 {
2132 Write-Output "[ERROR] Could not locate the CSV-Files directory ... Exiting"
2133 Return $null
2134 }
2135 Get-ADRExcelComObj
2136 If ($excel)
2137 {
2138 Write-Output "[*] Generating ADRecon-Report.xlsx"
2139
2140 $ADFileName = -join($ReportPath,'\','AboutADRecon.csv')
2141 If (Test-Path $ADFileName)
2142 {
2143 $worksheet= $workbook.Worksheets.Item(1)
2144 $worksheet.Name = "About ADRecon"
2145 Get-ADRExcelImport $ADFileName 1
2146 $worksheet.Hyperlinks.Add($worksheet.Cells.Item(3,2) , "https://github.com/sense-of-security/ADRecon", "" , "", "github.com/sense-of-security/ADRecon") | Out-Null
2147 $usedRange = $worksheet.UsedRange
2148 $usedRange.EntireColumn.AutoFit() | Out-Null
2149 }
2150
2151 $ADFileName = -join($ReportPath,'\','Forest.csv')
2152 If (Test-Path $ADFileName)
2153 {
2154 Get-ADRExcelWorkbook("AD Forest Info")
2155 Get-ADRExcelImport $ADFileName 1
2156 }
2157
2158 $ADFileName = -join($ReportPath,'\','Domain.csv')
2159 If (Test-Path $ADFileName)
2160 {
2161 Get-ADRExcelWorkbook("AD Domain Info")
2162 Get-ADRExcelImport $ADFileName 1
2163 $DomainObj = Import-CSV -Path $ADFileName
2164 $DomainName = -join($DomainObj.Name,"-")
2165 Remove-Variable DomainObj
2166 }
2167
2168 $ADFileName = -join($ReportPath,'\','DefaultPasswordPolicy.csv')
2169 If (Test-Path $ADFileName)
2170 {
2171 Get-ADRExcelWorkbook("Password Policy")
2172 Get-ADRExcelImport $ADFileName 1
2173 }
2174
2175 $ADFileName = -join($ReportPath,'\','DCs.csv')
2176 If (Test-Path $ADFileName)
2177 {
2178 Get-ADRExcelWorkbook("Domain Controllers")
2179 Get-ADRExcelImport $ADFileName 2
2180 }
2181
2182 $ADFileName = -join($ReportPath,'\','GPOs.csv')
2183 If (Test-Path $ADFileName)
2184 {
2185 Get-ADRExcelWorkbook("Domain GPOs")
2186 Get-ADRExcelImport $ADFileName 2
2187 }
2188
2189 $ADFileName = -join($ReportPath,'\','DNSNodes','.csv')
2190 If (Test-Path $ADFileName)
2191 {
2192 Get-ADRExcelWorkbook("DNS Records")
2193 Get-ADRExcelImport $ADFileName 2
2194 }
2195
2196 $ADFileName = -join($ReportPath,'\','DNSZones.csv')
2197 If (Test-Path $ADFileName)
2198 {
2199 Get-ADRExcelWorkbook("DNS Zones")
2200 Get-ADRExcelImport $ADFileName 2
2201 }
2202
2203 $ADFileName = -join($ReportPath,'\','Printers.csv')
2204 If (Test-Path $ADFileName)
2205 {
2206 Get-ADRExcelWorkbook("Printers")
2207 Get-ADRExcelImport $ADFileName 2
2208 }
2209
2210 $ADFileName = -join($ReportPath,'\','BitLockerRecoveryKeys.csv')
2211 If (Test-Path $ADFileName)
2212 {
2213 Get-ADRExcelWorkbook("BitLocker")
2214 Get-ADRExcelImport $ADFileName 2
2215 }
2216
2217 $ADFileName = -join($ReportPath,'\','LAPS.csv')
2218 If (Test-Path $ADFileName)
2219 {
2220 Get-ADRExcelWorkbook("LAPS")
2221 Get-ADRExcelImport $ADFileName 2
2222 }
2223
2224 $ADFileName = -join($ReportPath,'\','ComputerSPNs.csv')
2225 If (Test-Path $ADFileName)
2226 {
2227 $CompObj = Import-CSV -Path $ADFileName
2228 $ADCompStat = $CompObj | Sort-Object Name,Service -Unique | Select-Object Name,Service
2229 Remove-Variable CompObj
2230
2231 $ADFileName = -join($ReportPath,'\','ComputerSPNsStats.csv')
2232 $ADCompStat | Export-Csv -Path $ADFileName -NoTypeInformation
2233 Remove-Variable ADCompStat
2234
2235 Get-ADRExcelWorkbook("Computer SPNs")
2236 Get-ADRExcelImport $ADFileName 2
2237 }
2238
2239 $ADFileName = -join($ReportPath,'\','Computers.csv')
2240 If (Test-Path $ADFileName)
2241 {
2242 Get-ADRExcelWorkbook("Computers")
2243 Get-ADRExcelImport $ADFileName 2
2244
2245 $worksheet= $workbook.Worksheets.Item(1)
2246 If ($worksheet.Cells.Item(1,4).text -eq "IPv4Address")
2247 {
2248 [void] $worksheet.Cells.Item(1,4).Addcomment("May not be current.")
2249 }
2250 }
2251
2252 $ADFileName = -join($ReportPath,'\','OUPermissions.csv')
2253 If (Test-Path $ADFileName)
2254 {
2255 $Obj = Import-CSV -Path $ADFileName
2256 $TempObj = $Obj | Select-Object OrganizationalUnit,ObjectTypeName,ActiveDirectoryRights,IdentityReference,AccessControlType,isInherited
2257 Remove-Variable Obj
2258
2259 $ADFileName = -join($ReportPath,'\','OUPermissions1.csv')
2260 $TempObj | Export-Csv -Path $ADFileName -NoTypeInformation
2261 Remove-Variable TempObj
2262
2263 Get-ADRExcelWorkbook("OUPerms")
2264 Get-ADRExcelImport $ADFileName 2
2265
2266 $worksheet= $workbook.Worksheets.Item(1)
2267 $worksheet.Activate();
2268 $worksheet.Application.ActiveWindow.FreezePanes = $isFreeze
2269 $worksheet.Cells.Item(1,6).Interior.ColorIndex = 5
2270 $worksheet.Cells.Item(1,6).font.ColorIndex = 2
2271 # Set Filter to Explicitly Assigned Permissions Only
2272 $worksheet.UsedRange.Select() | Out-Null
2273 $excel.Selection.AutoFilter(6,$true) | Out-Null
2274 $worksheet.Range("A1").Select() | Out-Null
2275 }
2276
2277 $ADFileName = -join($ReportPath,'\','OUs.csv')
2278 If (Test-Path $ADFileName)
2279 {
2280 Get-ADRExcelWorkbook("OUs")
2281 Get-ADRExcelImport $ADFileName 2
2282 }
2283
2284 $ADFileName = -join($ReportPath,'\','UserSPNs.csv')
2285 If (Test-Path $ADFileName)
2286 {
2287 Get-ADRExcelWorkbook("User SPNs")
2288 Get-ADRExcelImport $ADFileName 2
2289 }
2290
2291 $ADFileName = -join($ReportPath,'\','Groups.csv')
2292 If (Test-Path $ADFileName)
2293 {
2294 Get-ADRExcelWorkbook("Groups")
2295 Get-ADRExcelImport $ADFileName 2
2296 }
2297
2298 $ADFileName = -join($ReportPath,'\','GroupMembers.csv')
2299 If (Test-Path $ADFileName)
2300 {
2301 Get-ADRExcelWorkbook("Group Members")
2302 Get-ADRExcelImport $ADFileName 2
2303 $filter = "Account Operators","Administrators","Backup Operators","Cert Publishers","Crypto Operators","Dns Admins","Domain Admins","Enterprise Admins","Incoming Forest Trust Builders","Network Operators","Print Operators","Schema Admins","Server Operators","Enterprise Key Admins","Key Admins"
2304 $xlFilterValues = 7
2305 $worksheet= $workbook.Worksheets.Item(1)
2306 $worksheet.Cells.Item(1,1).Interior.ColorIndex = 5
2307 $worksheet.Cells.Item(1,1).font.ColorIndex = 2
2308 $worksheet.UsedRange.AutoFilter(1,$filter,$xlFilterValues) | Out-Null
2309 }
2310
2311 $ADFileName = -join($ReportPath,'\','Users.csv')
2312 If (Test-Path $ADFileName)
2313 {
2314 Get-ADRExcelWorkbook("Users")
2315 Get-ADRExcelImport $ADFileName 2
2316
2317 $worksheet= $workbook.Worksheets.Item(1)
2318 $worksheet.Activate();
2319 $worksheet.Application.ActiveWindow.FreezePanes = $isFreeze
2320 $worksheet.Cells.Item(1,3).Interior.ColorIndex = 5
2321 $worksheet.Cells.Item(1,3).font.ColorIndex = 2
2322 # Set Filter to Enabled Accounts only
2323 $worksheet.UsedRange.Select() | Out-Null
2324 $excel.Selection.AutoFilter(3,$true) | Out-Null
2325 $worksheet.Range("A1").Select() | Out-Null
2326 }
2327
2328 $ADFileName = -join($ReportPath,'\','Computers.csv')
2329 If (Test-Path $ADFileName)
2330 {
2331 $CompObj = Import-CSV -Path $ADFileName
2332 $ADCompStat = $CompObj | Select-Object OperatingSystem | Group-Object -Property OperatingSystem | Sort-Object -Property @{Expression="Count";Descending=$true}
2333 Remove-Variable CompObj
2334
2335 Get-ADRExcelWorkbook("Computer Stats")
2336 $worksheet= $workbook.Worksheets.Item(1)
2337
2338 $row = 1
2339 $column = 1
2340 "Operating System","Count" | ForEach-Object {
2341 $worksheet.Cells.Item($row,$column)=$_
2342 $column++
2343 }
2344 $column = 1
2345 $row = 2
2346 $ADCompStat | ForEach-Object {
2347 $worksheet.Cells.Item($row,$column) = $_.Name
2348 $column++
2349 $worksheet.Cells.Item($row,$column) = $_.Count
2350 $column=1
2351 $row++
2352 }
2353 Remove-Variable ADCompStat
2354 $listObject = $worksheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheet.UsedRange, $null, [Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes, $null)
2355 $listObject.TableStyle = "TableStyleLight2" # Style Cheat Sheet: https://msdn.microsoft.com/en-au/library/documentformat.openxml.spreadsheet.tablestyle.aspx
2356 $usedRange = $worksheet.UsedRange
2357 $usedRange.EntireColumn.AutoFit() | Out-Null
2358
2359 #Add Pie Chart
2360 #Get-ADRExcelChart $ChartType $ChartLayout $ChartTitle $RangetoCover $chardata
2361 Get-ADRExcelChart 51 10 "Operating Systems in AD" "D2:S16" $null
2362 }
2363
2364 $ADFileName = -join($ReportPath,'\','ComputerSPNsStats.csv')
2365 If (Test-Path $ADFileName)
2366 {
2367 $CompObj = Import-CSV -Path $ADFileName
2368 $ADCompStat = $CompObj | Group-Object -Property Service | Sort-Object -Property @{Expression="Count";Descendin=$true}
2369 Remove-Variable CompObj
2370
2371 Get-ADRExcelWorkbook("Computer Role Stats")
2372 $worksheet= $workbook.Worksheets.Item(1)
2373
2374 $row = 1
2375 $column = 1
2376 "Computer Role","Count" | ForEach-Object {
2377 $worksheet.Cells.Item($row,$column)=$_
2378 $column++
2379 }
2380 $column = 1
2381 $row = 2
2382 $ADCompStat | ForEach-Object {
2383 $worksheet.Cells.Item($row,$column) = $_.Name
2384 $column++
2385 $worksheet.Cells.Item($row,$column) = $_.Count
2386 $column=1
2387 $row++
2388 }
2389 Remove-Variable ADCompStat
2390 $listObject = $worksheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheet.UsedRange, $null, [Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes, $null)
2391 $listObject.TableStyle = "TableStyleLight2" # Style Cheat Sheet: https://msdn.microsoft.com/en-au/library/documentformat.openxml.spreadsheet.tablestyle.aspx
2392 $usedRange = $worksheet.UsedRange
2393 $usedRange.EntireColumn.AutoFit() | Out-Null
2394
2395 #Add Pie Chart
2396 #Get-ADRExcelChart $ChartType $ChartLayout $ChartTitle $RangetoCover $chardata
2397 Get-ADRExcelChart 51 10 "Computer Roles in AD" "D2:U16" $null
2398 }
2399
2400 $ADFileName = -join($ReportPath,'\','GroupMembers.csv')
2401 If (Test-Path $ADFileName)
2402 {
2403 $GroupObj = Import-CSV -Path $ADFileName
2404 $ADGroupStat = $GroupObj | Where-Object {$_.'AccountType' -eq 'user'} | Select-Object 'Group Name' | Group-Object -Property 'Group Name' | Sort-Object -Property @{Expression="Count";Descending=$true}
2405 Remove-Variable GroupObj
2406
2407 Get-ADRExcelWorkbook("Privileged User Group Stats")
2408 $worksheet= $workbook.Worksheets.Item(1)
2409 $row = 1
2410 $column = 1
2411 $worksheet.Cells.Item($row,$column).Interior.ColorIndex = 5
2412 $worksheet.Cells.Item($row,$column).font.ColorIndex = 2
2413 "Group Name","User Count (Not-Recursive)" | ForEach-Object {
2414 $worksheet.Cells.Item($row,$column)=$_
2415 $column++
2416 }
2417 $column = 1
2418 $row = 2
2419 $ADGroupStat | ForEach-Object {
2420 $worksheet.Cells.Item($row,$column) = $_.Name
2421 $column++
2422 $worksheet.Cells.Item($row,$column) = $_.Count
2423 $column=1
2424 $row++
2425 }
2426 Remove-Variable ADGroupStat
2427
2428 $filter = "Account Operators","Administrators","Backup Operators","Cert Publishers","Crypto Operators","Dns Admins","Domain Admins","Enterprise Admins","Incoming Forest Trust Builders","Network Operators","Print Operators","Schema Admins","Server Operators","Enterprise Key Admins","Key Admins"
2429 $xlFilterValues = 7
2430 $worksheet= $workbook.Worksheets.Item(1)
2431 $worksheet.UsedRange.AutoFilter(1,$filter,$xlFilterValues) | Out-Null
2432
2433 $listObject = $worksheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheet.UsedRange, $null, [Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes, $null)
2434 $listObject.TableStyle = "TableStyleLight2" # Style Cheat Sheet: https://msdn.microsoft.com/en-au/library/documentformat.openxml.spreadsheet.tablestyle.aspx
2435 $usedRange = $worksheet.UsedRange
2436 $usedRange.EntireColumn.AutoFit() | Out-Null
2437
2438 #Get-ADRExcelChart $ChartType $ChartLayout $ChartTitle $RangetoCover $chardata
2439 Get-ADRExcelChart 51 10 "Privileged Groups in AD" "D2:P16" $null
2440 }
2441
2442 $ADFileName = -join($ReportPath,'\','Users.csv')
2443 If (Test-Path $ADFileName)
2444 {
2445 Get-ADRExcelWorkbook("User Stats")
2446 $worksheet= $workbook.Worksheets.Item(1)
2447
2448 $ADTemp = Import-Csv -Path $ADFileName
2449
2450 $row = 1
2451 $column = 1
2452 $worksheet.Cells.Item($row, $column) = "User Accounts in AD"
2453 $worksheet.Cells.Item($row,$column).Style = "Heading 2"
2454 $worksheet.Cells.Item($row,$column).HorizontalAlignment = -4108
2455 $MergeCells = $worksheet.Range("A1:C1")
2456 $MergeCells.Select() | Out-Null
2457 $MergeCells.MergeCells = $true
2458 Remove-Variable MergeCells
2459
2460 $row++
2461 $worksheet.Cells.Item($row, $column) = "Type"
2462 $worksheet.Cells.Item($row, $column).Font.Bold=$True
2463 $worksheet.Cells.Item($row, $column+1) = "Count"
2464 $worksheet.Cells.Item($row, $column+1).Font.Bold=$True
2465 $worksheet.Cells.Item($row,$column+2) = 'Percentage'
2466 $worksheet.Cells.Item($row, $column+2).Font.Bold=$True
2467
2468 $total = ($ADTemp | Measure-Object | Select-Object -ExpandProperty Count)
2469 $enabled = ($ADTemp | Where-Object ({$_.Enabled -eq $true}) | Measure-Object | Select-Object -ExpandProperty Count)
2470 $disabled = ($ADTemp | Where-Object ({$_.Enabled -eq $false}) | Measure-Object | Select-Object -ExpandProperty Count)
2471
2472 $row++
2473 $worksheet.Cells.Item($row, $column) = "Enabled"
2474 $worksheet.Cells.Item($row, $column+1) = $enabled
2475 $worksheet.Cells.Item($row, $column+2) = "{0:P2}" -f ($enabled/$total)
2476
2477 $row++
2478 $worksheet.Cells.Item($row, $column) = "Disabled"
2479 $worksheet.Cells.Item($row, $column+1) = $disabled
2480 $worksheet.Cells.Item($row, $column+2) = "{0:P2}" -f ($disabled/$total)
2481
2482 $row++
2483 $worksheet.Cells.Item($row, $column) = "Total"
2484 $worksheet.Cells.Item($row, $column+1) = $total
2485 If ($total -ne ($enabled + $disabled))
2486 {
2487 $worksheet.Cells.Item($row, $column+1).Interior.ColorIndex = 3
2488 $worksheet.Cells.Item($row, $column+1).font.ColorIndex = 2
2489 Write-Warning "Enabled + Disabled != Total Users, Try running ADRecon as another user."
2490 }
2491 $worksheet.Cells.Item($row, $column+2) = "{0:P2}" -f ($total/$total)
2492
2493 #Get-ADRExcelChart $ChartType $ChartLayout $ChartTitle $RangetoCover $chardata
2494 Get-ADRExcelChart 5 3 "User Accounts in AD" "A14:D26" $worksheet.Range("A3:A4,B3:B4")
2495
2496 $row = 1
2497 $column = 6
2498 $worksheet.Cells.Item($row, $column) = "Status of User Accounts"
2499 $worksheet.Cells.Item($row,$column).Style = "Heading 2"
2500 $worksheet.Cells.Item($row,$column).HorizontalAlignment = -4108
2501 $MergeCells = $worksheet.Range("F1:J1")
2502 $MergeCells.Select() | Out-Null
2503 $MergeCells.MergeCells = $true
2504 Remove-Variable MergeCells
2505
2506 $row++
2507 $temp = @("Category","Enabled Count","Disabled Count","Enabled Percentage","Disabled Percentage")
2508 $temp | ForEach-Object {
2509 $worksheet.Cells.Item($row, $column) = $_
2510 $worksheet.Cells.Item($row, $column).Font.Bold=$True
2511 $column++
2512 }
2513
2514 $column = 6
2515 $UserProperties = @("Cannot Change Password","Must Change Password at Logon","Password Not Changed after Max Age","Password Never Expires","Password Not Required","Reversibly Encryped Password","Does Not Require Pre Auth","Account Locked Out","Never Logged in",$(($ADTemp | Get-Member -MemberType NoteProperty | Where-Object { $_.Name -like "Dormant*" }).Name))
2516 ForEach ($property in $UserProperties)
2517 {
2518 $row++
2519 $worksheet.Cells.Item($row, $column) = $property
2520 $worksheet.Cells.Item($row, $column+1) = ($ADTemp | Where-Object ({$_.$property -eq $true -and $_.Enabled -eq $true}) | Measure-Object | Select-Object -ExpandProperty Count)
2521 $worksheet.Cells.Item($row, $column+2) = ($ADTemp | Where-Object ({$_.$property -eq $true -and $_.Enabled -eq $false}) | Measure-Object | Select-Object -ExpandProperty Count)
2522 $worksheet.Cells.Item($row, $column+3) = "{0:P2}" -f (([int] $worksheet.Cells.Item($row,$column+1).text)/$total)
2523 $worksheet.Cells.Item($row, $column+4) = "{0:P2}" -f (([int] $worksheet.Cells.Item($row,$column+2).text)/$total)
2524 }
2525
2526 #Get-ADRExcelChart $ChartType $ChartLayout $ChartTitle $RangetoCover $chardata
2527 Get-ADRExcelChart 51 5 "Status of User Accounts" "F14:J36" $worksheet.Range("F2:F12,G2:H12")
2528
2529 Remove-Variable ADTemp
2530 $usedRange = $worksheet.UsedRange
2531 $usedRange.EntireColumn.AutoFit() | Out-Null
2532 }
2533
2534 # Create Table of Contents
2535
2536 Get-ADRExcelWorkbook("Table of Contents")
2537 $worksheet= $workbook.Worksheets.Item(1)
2538
2539 # Image format and properties
2540 # $path = "C:\SOS_Logo.jpg"
2541 # $base64sos = [convert]::ToBase64String((Get-Content $path -Encoding byte))
2542
2543 #$base64sos = "/9j/"
2544
2545 #$bytes = [System.Convert]::FromBase64String($base64sos)
2546 #Remove-Variable base64sos
2547
2548 #$CompanyLogo = -join($ReportPath,'\','SOS_Logo.jpg')
2549 #$p = New-Object IO.MemoryStream($bytes, 0, $bytes.length)
2550 #$p.Write($bytes, 0, $bytes.length)
2551 #Add-Type -AssemblyName System.Drawing
2552 #$picture = [System.Drawing.Image]::FromStream($p, $true)
2553 #$picture.Save($CompanyLogo)
2554
2555 #Remove-Variable bytes
2556 #Remove-Variable p
2557 #Remove-Variable picture
2558
2559 #$LinkToFile = $false
2560 #$SaveWithDocument = $true
2561 #$Left = 0
2562 #$Top = 0
2563 #$Width = 135
2564 #$Height = 50
2565
2566 # Add image to the Sheet
2567 #$worksheet.Shapes.AddPicture($CompanyLogo, $LinkToFile, $SaveWithDocument, $Left, $Top, $Width, $Height) | Out-Null
2568
2569 #Remove-Variable LinkToFile
2570 #Remove-Variable SaveWithDocument
2571 #Remove-Variable Left
2572 #Remove-Variable Top
2573 #Remove-Variable Width
2574 #Remove-Variable Height
2575
2576 $row = 5
2577 $column = 1
2578 $worksheet.Cells.Item($row,$column)= "Table of Contents"
2579 $worksheet.Cells.Item($row,$column).Style = "Heading 2"
2580 $row++
2581
2582 For($i=2; $i -le $workbook.Worksheets.Count; $i++)
2583 {
2584 $workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item($row,$column) , "" , "'$($workbook.Worksheets.Item($i).Name)'!A1", "", $workbook.Worksheets.Item($i).Name) | Out-Null
2585 $row++
2586 }
2587
2588 $row++
2589 #$worksheet.Cells.Item($row, 1) = "© Sense of Security 2018"
2590 #$workbook.Worksheets.Item(1).Hyperlinks.Add($workbook.Worksheets.Item(1).Cells.Item($row,2) , "https://www.senseofsecurity.com.au", "" , "", "www.senseofsecurity.com.au") | Out-Null
2591
2592 $usedRange = $worksheet.UsedRange
2593 $usedRange.EntireColumn.AutoFit() | Out-Null
2594
2595 $excel.Windows.Item(1).Displaygridlines=$false
2596
2597 $ADStatFileName = -join($ExcelPath,'\',$DomainName,'ADRecon-Report','.xlsx')
2598 Try
2599 {
2600 # Disable prompt if file exists
2601 $excel.DisplayAlerts = $False
2602 $workbook.SaveAs($ADStatFileName)
2603 Write-Output "[+] Excelsheet Saved to: $ADStatFileName"
2604 }
2605 Catch
2606 {
2607 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2608 }
2609 [gc]::Collect()
2610 [gc]::WaitForPendingFinalizers()
2611 $excel.Quit()
2612 [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject] $excel) | Out-Null
2613 }
2614}
2615
2616Function Get-ADRDomain
2617{
2618<#
2619.SYNOPSIS
2620 Returns information of the current (or specified) domain.
2621
2622.DESCRIPTION
2623 Returns information of the current (or specified) domain.
2624
2625.PARAMETER Protocol
2626 [string]
2627 Which protocol to use; ADWS (default) or LDAP.
2628
2629.PARAMETER UseAltCreds
2630 [bool]
2631 Whether to use provided credentials or not.
2632
2633.PARAMETER ReportPath
2634 [string]
2635 Path for ADRecon output folder.
2636
2637.PARAMETER objDomain
2638 [DirectoryServices.DirectoryEntry]
2639 Domain Directory Entry object.
2640
2641.PARAMETER objDomainRootDSE
2642 [DirectoryServices.DirectoryEntry]
2643 RootDSE Directory Entry object.
2644
2645.PARAMETER DCIP
2646 [string]
2647 IP Address of the Domain Controller.
2648
2649.PARAMETER creds
2650 [Management.Automation.PSCredential]
2651 Credentials.
2652
2653.PARAMETER OutputType
2654 [array]
2655 Output Type.
2656
2657.OUTPUTS
2658 CSV file is created in the folder specified with the information.
2659#>
2660 param(
2661 [Parameter(Mandatory = $true)]
2662 [string] $Protocol,
2663
2664 [Parameter(Mandatory = $true)]
2665 [bool] $UseAltCreds,
2666
2667 [Parameter(Mandatory = $true)]
2668 [string] $ReportPath,
2669
2670 [Parameter(Mandatory = $false)]
2671 [DirectoryServices.DirectoryEntry] $objDomain,
2672
2673 [Parameter(Mandatory = $false)]
2674 [DirectoryServices.DirectoryEntry] $objDomainRootDSE,
2675
2676 [Parameter(Mandatory = $false)]
2677 [string] $DCIP,
2678
2679 [Parameter(Mandatory = $false)]
2680 [Management.Automation.PSCredential] $creds = [Management.Automation.PSCredential]::Empty,
2681
2682 [Parameter(Mandatory = $true)]
2683 [array] $OutputType
2684 )
2685
2686 Write-Output "[-] Domain"
2687 If ($Protocol -eq 'ADWS')
2688 {
2689 Try
2690 {
2691 $ADDomain = Get-ADDomain
2692 }
2693 Catch
2694 {
2695 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2696 Return $null
2697 }
2698 If ($ADDomain)
2699 {
2700 $ADDomainObj = New-Object PSObject
2701 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Value"
2702 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Name" -Value $ADDomain.DNSRoot
2703 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "NetBIOS" -Value $ADDomain.NetBIOSName
2704 # Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
2705 $FLAD = @{
2706 0 = "Windows2000";
2707 1 = "Windows2003/Interim";
2708 2 = "Windows2003";
2709 3 = "Windows2008";
2710 4 = "Windows2008R2";
2711 5 = "Windows2012";
2712 6 = "Windows2012R2";
2713 7 = "Windows2016"
2714 }
2715 $DomainMode = $FLAD[[convert]::ToInt32($ADDomain.DomainMode)] + "Domain"
2716 Remove-Variable FLAD
2717 If ($DomainMode)
2718 {
2719 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Functional Level" -Value $DomainMode
2720 Remove-Variable DomainMode
2721 }
2722 Else
2723 {
2724 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Functional Level" -Value $ADDomain.DomainMode
2725 }
2726 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "DomainSID "-Value $ADDomain.DomainSID.Value
2727 For($i=0; $i -lt $ADDomain.ReplicaDirectoryServers.Count; $i++)
2728 {
2729 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Domain Controller -$i" -Value $ADDomain.ReplicaDirectoryServers[$i]
2730 }
2731 For($i=0; $i -lt $ADDomain.ReadOnlyReplicaDirectoryServers.Count; $i++)
2732 {
2733 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Read Only Domain Controller -$i" -Value $ADDomain.ReadOnlyReplicaDirectoryServers[$i]
2734 }
2735
2736 Try
2737 {
2738 $ADForest = Get-ADForest $ADDomain.Forest
2739 }
2740 Catch
2741 {
2742 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2743 }
2744
2745 If (-Not $ADForest)
2746 {
2747 Try
2748 {
2749 $ADForest = Get-ADForest -Server $DCIP
2750 }
2751 Catch
2752 {
2753 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2754 }
2755 }
2756 If ($ADForest)
2757 {
2758 $DomainCreation = Get-ADObject -SearchBase "$($ADForest.PartitionsContainer)" -LDAPFilter "(&(objectClass=crossRef)(systemFlags=3)(Name=$($ADDomain.Name)))" -Properties whenCreated
2759 If (-Not $DomainCreation)
2760 {
2761 $DomainCreation = Get-ADObject -SearchBase "$($ADForest.PartitionsContainer)" -LDAPFilter "(&(objectClass=crossRef)(systemFlags=3)(Name=$($ADDomain.NetBIOSName)))" -Properties whenCreated
2762 }
2763 Remove-Variable ADForest
2764 }
2765 # Get RIDAvailablePool
2766 Try
2767 {
2768 $RIDManager = Get-ADObject -Identity "CN=RID Manager$,CN=System,$($ADDomain.DistinguishedName)" -Properties rIDAvailablePool
2769 $RIDproperty = $RIDManager.rIDAvailablePool
2770 [int32] $totalSIDS = $($RIDproperty) / ([math]::Pow(2,32))
2771 [int64] $temp64val = $totalSIDS * ([math]::Pow(2,32))
2772 $RIDsIssued = [int32]($($RIDproperty) - $temp64val)
2773 $RIDsRemaining = $totalSIDS - $RIDsIssued
2774 Remove-Variable RIDManager
2775 Remove-Variable RIDproperty
2776 Remove-Variable totalSIDS
2777 Remove-Variable temp64val
2778 }
2779 Catch
2780 {
2781 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2782 }
2783 If ($DomainCreation)
2784 {
2785 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Creation Date" -Value $DomainCreation.whenCreated
2786 Remove-Variable DomainCreation
2787 }
2788 If ($RIDsIssued)
2789 {
2790 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "RIDs Issued" -Value $RIDsIssued
2791 Remove-Variable RIDsIssued
2792 }
2793 If ($RIDsRemaining)
2794 {
2795 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "RIDs Remaining" -Value $RIDsRemaining
2796 Remove-Variable RIDsRemaining
2797 }
2798 }
2799 }
2800
2801 If ($Protocol -eq 'LDAP')
2802 {
2803 If ($UseAltCreds)
2804 {
2805 $DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
2806 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($creds.UserName),$($creds.GetNetworkCredential().password))
2807 Try
2808 {
2809 $ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
2810 }
2811 Catch
2812 {
2813 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2814 Return $null
2815 }
2816 Remove-Variable DomainContext
2817 # Get RIDAvailablePool
2818 $SearchPath = "CN=RID Manager$,CN=System"
2819 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$SearchPath,$($objDomain.distinguishedName)", $creds.UserName,$creds.GetNetworkCredential().Password
2820 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
2821 $objSearcherPath.PropertiesToLoad.AddRange(("ridavailablepool"))
2822 $objSearcherResult = $objSearcherPath.FindAll()
2823 $RIDproperty = $objSearcherResult.Properties.ridavailablepool
2824 [int32] $totalSIDS = $($RIDproperty) / ([math]::Pow(2,32))
2825 [int64] $temp64val = $totalSIDS * ([math]::Pow(2,32))
2826 $RIDsIssued = [int32]($($RIDproperty) - $temp64val)
2827 $RIDsRemaining = $totalSIDS - $RIDsIssued
2828 Remove-Variable SearchPath
2829 $objSearchPath.Dispose()
2830 $objSearcherPath.Dispose()
2831 $objSearcherResult.Dispose()
2832 Remove-Variable RIDproperty
2833 Remove-Variable totalSIDS
2834 Remove-Variable temp64val
2835 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$($ADDomain.Forest),$($creds.UserName),$($creds.GetNetworkCredential().password))
2836 Try
2837 {
2838 $ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
2839 }
2840 Catch
2841 {
2842 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2843 Return $null
2844 }
2845 Remove-Variable ForestContext
2846
2847 $GlobalCatalog = $ADForest.FindGlobalCatalog()
2848 If ($GlobalCatalog)
2849 {
2850 $DN = "GC://$($GlobalCatalog.IPAddress)/$($objDomain.distinguishedname)"
2851 $ADObject = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList ($($DN),$($creds.UserName),$($creds.GetNetworkCredential().password))
2852 $ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($ADObject.objectSid[0], 0)
2853 $ADObject.Dispose()
2854 }
2855 }
2856 Else
2857 {
2858 $ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
2859 $ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
2860 Try
2861 {
2862 $GlobalCatalog = $ADForest.FindGlobalCatalog()
2863 $DN = "GC://$($GlobalCatalog)/$($objDomain.distinguishedname)"
2864 $ADObject = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList ($DN)
2865 $ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($ADObject.objectSid[0], 0)
2866 $ADObject.dispose()
2867 }
2868 Catch
2869 {
2870 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2871 $ADDomainSID = New-Object System.Security.Principal.SecurityIdentifier($objDomain.objectSid[0], 0)
2872 }
2873 # Get RIDAvailablePool
2874 $RIDManager = ([ADSI]"LDAP://CN=RID Manager$,CN=System,$($objDomain.distinguishedName)")
2875 $RIDproperty = $ObjDomain.ConvertLargeIntegerToInt64($RIDManager.Properties.rIDAvailablePool.value)
2876 [int32] $totalSIDS = $($RIDproperty) / ([math]::Pow(2,32))
2877 [int64] $temp64val = $totalSIDS * ([math]::Pow(2,32))
2878 $RIDsIssued = [int32]($($RIDproperty) - $temp64val)
2879 $RIDsRemaining = $totalSIDS - $RIDsIssued
2880 Remove-Variable RIDManager
2881 Remove-Variable RIDproperty
2882 Remove-Variable totalSIDS
2883 Remove-Variable temp64val
2884 }
2885
2886 If ($ADDomain)
2887 {
2888 $ADDomainObj = New-Object PSObject
2889 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Value"
2890 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Name" -Value $ADDomain.Name
2891 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "NetBIOS" -Value $objDomain.name.value
2892 # Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
2893 $FLAD = @{
2894 0 = "Windows2000";
2895 1 = "Windows2003/Interim";
2896 2 = "Windows2003";
2897 3 = "Windows2008";
2898 4 = "Windows2008R2";
2899 5 = "Windows2012";
2900 6 = "Windows2012R2";
2901 7 = "Windows2016"
2902 }
2903 $DomainMode = $FLAD[[convert]::ToInt32($objDomainRootDSE.domainFunctionality,10)] + "Domain"
2904 Remove-Variable FLAD
2905 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Functional Level" -Value $DomainMode
2906 Remove-Variable DomainMode
2907 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "DomainSID "-Value $ADDomainSID.Value
2908 For($i=0; $i -lt $ADDomain.DomainControllers.Count; $i++)
2909 {
2910 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Domain Controller -$i" -Value $ADDomain.DomainControllers[$i]
2911 }
2912 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "Creation Date" -Value $objDomain.whencreated.value
2913 If ($RIDsIssued)
2914 {
2915 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "RIDs Issued" -Value $RIDsIssued
2916 Remove-Variable RIDsIssued
2917 }
2918 If ($RIDsRemaining)
2919 {
2920 $ADDomainObj | Add-Member -MemberType NoteProperty -Name "RIDs Remaining" -Value $RIDsRemaining
2921 Remove-Variable RIDsRemaining
2922 }
2923 }
2924 }
2925
2926 If ($ADDomainObj)
2927 {
2928 Switch ($OutputType)
2929 {
2930 'STDOUT' { $ADDomainObj }
2931 'CSV'
2932 {
2933 Write-Verbose "[+] Domain"
2934 $ADFileName = -join($ReportPath,'\','Domain','.csv')
2935 Try
2936 {
2937 $ADDomainObj | Export-Csv -Path $ADFileName -NoTypeInformation
2938 }
2939 Catch
2940 {
2941 Write-Output "[ERROR] Failed to Export CSV File"
2942 Write-Output "[EXCEPTION] $($_.Exception.Message)"
2943 }
2944 Remove-Variable ADDomainObj
2945 Remove-Variable ADFileName
2946 }
2947 }
2948 }
2949}
2950
2951Function Get-ADRForest
2952{
2953<#
2954.SYNOPSIS
2955 Returns information of the current (or specified) forest.
2956
2957.DESCRIPTION
2958 Returns information of the current (or specified) forest.
2959
2960.PARAMETER Protocol
2961 [string]
2962 Which protocol to use; ADWS (default) or LDAP.
2963
2964.PARAMETER UseAltCreds
2965 [bool]
2966 Whether to use provided credentials or not.
2967
2968.PARAMETER ReportPath
2969 [string]
2970 Path for ADRecon output folder.
2971
2972.PARAMETER objDomain
2973 [DirectoryServices.DirectoryEntry]
2974 Domain Directory Entry object.
2975
2976.PARAMETER objDomainRootDSE
2977 [DirectoryServices.DirectoryEntry]
2978 RootDSE Directory Entry object.
2979
2980.PARAMETER DCIP
2981 [string]
2982 IP Address of the Domain Controller.
2983
2984.PARAMETER creds
2985 [Management.Automation.PSCredential]
2986 Credentials.
2987
2988.PARAMETER OutputType
2989 [array]
2990 Output Type.
2991
2992.OUTPUTS
2993 CSV file is created in the folder specified with the information.
2994#>
2995 param(
2996 [Parameter(Mandatory = $true)]
2997 [string] $Protocol,
2998
2999 [Parameter(Mandatory = $true)]
3000 [bool] $UseAltCreds,
3001
3002 [Parameter(Mandatory = $true)]
3003 [string] $ReportPath,
3004
3005 [Parameter(Mandatory = $false)]
3006 [DirectoryServices.DirectoryEntry] $objDomain,
3007
3008 [Parameter(Mandatory = $false)]
3009 [DirectoryServices.DirectoryEntry] $objDomainRootDSE,
3010
3011 [Parameter(Mandatory = $false)]
3012 [string] $DCIP,
3013
3014 [Parameter(Mandatory = $false)]
3015 [Management.Automation.PSCredential] $creds = [Management.Automation.PSCredential]::Empty,
3016
3017 [Parameter(Mandatory = $true)]
3018 [array] $OutputType
3019 )
3020
3021 Write-Output "[-] Forest"
3022 If ($Protocol -eq 'ADWS')
3023 {
3024 Try
3025 {
3026 $ADDomain = Get-ADDomain
3027 }
3028 Catch
3029 {
3030 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3031 Return $null
3032 }
3033
3034 Try
3035 {
3036 $ADForest = Get-ADForest $ADDomain.Forest
3037 }
3038 Catch
3039 {
3040 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3041 }
3042 Remove-Variable ADDomain
3043
3044 If (-Not $ADForest)
3045 {
3046 Try
3047 {
3048 $ADForest = Get-ADForest -Server $DCIP
3049 }
3050 Catch
3051 {
3052 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3053 Return $null
3054 }
3055 }
3056
3057 If ($ADForest)
3058 {
3059 Try
3060 {
3061 $ADRecycleBin = Get-ADOptionalFeature -Filter 'name -like "Recycle Bin Feature"'
3062 }
3063 Catch
3064 {
3065 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3066 }
3067
3068 Try
3069 {
3070 $ADForestCNC = (Get-ADRootDSE).configurationNamingContext
3071 $ADForestDSCP = Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$($ADForestCNC)" -Partition $ADForestCNC -Properties *
3072 $ADForestTombstoneLifetime = $ADForestDSCP.tombstoneLifetime
3073 Remove-Variable ADForestCNC
3074 Remove-Variable ADForestDSCP
3075 }
3076 Catch
3077 {
3078 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3079 }
3080
3081 $ADForestObj = New-Object PSObject
3082 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Value"
3083 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Name" -Value $ADForest.Name
3084 # Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
3085 $FLAD = @{
3086 0 = "Windows2000";
3087 1 = "Windows2003/Interim";
3088 2 = "Windows2003";
3089 3 = "Windows2008";
3090 4 = "Windows2008R2";
3091 5 = "Windows2012";
3092 6 = "Windows2012R2";
3093 7 = "Windows2016"
3094 }
3095 $ForestMode = $FLAD[[convert]::ToInt32($ADForest.ForestMode)] + "Forest"
3096 Remove-Variable FLAD
3097 If ($ForestMode)
3098 {
3099 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Functional Level" -Value $ForestMode
3100 Remove-Variable ForestMode
3101 }
3102 Else
3103 {
3104 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Functional Level" -Value $ADForest.ForestMode
3105 }
3106 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Domain Naming Master" -Value $ADForest.DomainNamingMaster
3107 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Schema Master" -Value $ADForest.SchemaMaster
3108 $ADForestObj | Add-Member -MemberType NoteProperty -Name "RootDomain" -Value $ADForest.RootDomain
3109 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Domain Count" -Value $ADForest.Domains.Count
3110 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Site Count" -Value $ADForest.Sites.Count
3111 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Global Catalog Count" -Value $ADForest.GlobalCatalogs.Count
3112 For($i=0; $i -lt $ADForest.Domains.Count; $i++)
3113 {
3114 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Domain -$i" -Value $ADForest.Domains[$i]
3115 }
3116 For($i=0; $i -lt $ADForest.Sites.Count; $i++)
3117 {
3118 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Site -$i" -Value $ADForest.Sites[$i]
3119 }
3120 For($i=0; $i -lt $ADForest.GlobalCatalogs.Count; $i++)
3121 {
3122 $ADForestObj | Add-Member -MemberType NoteProperty -Name "GlobalCatalog -$i" -Value $ADForest.GlobalCatalogs[$i]
3123 }
3124 Remove-Variable ADForest
3125 If ($ADRecycleBin)
3126 {
3127 If ($ADRecycleBin.EnabledScopes.Count -eq 0)
3128 {
3129 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Recycle Bin Enabled" -Value $false
3130 }
3131 Else
3132 {
3133 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Recycle Bin Enabled" -Value $true
3134 }
3135 Remove-Variable ADRecycleBin
3136 }
3137 If ($ADForestTombstoneLifetime)
3138 {
3139 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Tombstone Lifetime" -Value $ADForestTombstoneLifetime
3140 Remove-Variable ADForestTombstoneLifetime
3141 }
3142 Else
3143 {
3144 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Tombstone Lifetime" -Value "Not Retrieved"
3145 }
3146 }
3147 }
3148
3149 If ($Protocol -eq 'LDAP')
3150 {
3151 If ($UseAltCreds)
3152 {
3153 $DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
3154 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($creds.UserName),$($creds.GetNetworkCredential().password))
3155 Try
3156 {
3157 $ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
3158 }
3159 Catch
3160 {
3161 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3162 Return $null
3163 }
3164 Remove-Variable DomainContext
3165
3166 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$($ADDomain.Forest),$($creds.UserName),$($creds.GetNetworkCredential().password))
3167 Remove-Variable ADDomain
3168 Try
3169 {
3170 $ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
3171 }
3172 Catch
3173 {
3174 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3175 Return $null
3176 }
3177 Remove-Variable ForestContext
3178
3179 # Check AD Recycle Bin Status
3180 Try
3181 {
3182 $SearchPath = "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration"
3183 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$($SearchPath),$($objDomain.distinguishedName)", $creds.UserName,$creds.GetNetworkCredential().Password
3184 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
3185 $ADRecycleBin = $objSearcherPath.FindAll()
3186 Remove-Variable SearchPath
3187 $objSearchPath.Dispose()
3188 $objSearcherPath.Dispose()
3189 }
3190 Catch
3191 {
3192 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3193 }
3194
3195 # Get Tombstone Lifetime
3196 $SearchPath = "CN=Directory Service,CN=Windows NT,CN=Services"
3197 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$SearchPath,$($objDomainRootDSE.configurationNamingContext)", $creds.UserName,$creds.GetNetworkCredential().Password
3198 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
3199 $objSearcherPath.Filter="(name=Directory Service)"
3200 $objSearcherResult = $objSearcherPath.FindAll()
3201 $ADForestTombstoneLifetime = $objSearcherResult.Properties.tombstoneLifetime
3202 Remove-Variable SearchPath
3203 $objSearchPath.Dispose()
3204 $objSearcherPath.Dispose()
3205 $objSearcherResult.Dispose()
3206
3207 }
3208 Else
3209 {
3210 $ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
3211 $ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
3212
3213 # Check AD Recycle Bin Status
3214 $ADRecycleBin = ([ADSI]"LDAP://CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$($objDomain.distinguishedName)")
3215
3216 # Get Tombstone Lifetime
3217 $ADForestTombstoneLifetime = ([ADSI]"LDAP://CN=Directory Service,CN=Windows NT,CN=Services,$($objDomainRootDSE.configurationNamingContext)").tombstoneLifetime.value
3218
3219 }
3220
3221 # Values taken from https://technet.microsoft.com/en-us/library/hh852281(v=wps.630).aspx
3222 $FLAD = @{
3223 0 = "Windows2000";
3224 1 = "Windows2003/Interim";
3225 2 = "Windows2003";
3226 3 = "Windows2008";
3227 4 = "Windows2008R2";
3228 5 = "Windows2012";
3229 6 = "Windows2012R2";
3230 7 = "Windows2016"
3231 }
3232
3233 If ($ADForest)
3234 {
3235 $ADForestObj = New-Object PSObject
3236 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Category" -Value "Value"
3237 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Name" -Value $ADForest.Name
3238 $ForestMode = $FLAD[[convert]::ToInt32($objDomainRootDSE.forestFunctionality,10)] + "Forest"
3239 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Functional Level" -Value $ForestMode
3240 Remove-Variable ForestMode
3241 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Domain Naming Master" -Value $ADForest.NamingRoleOwner
3242 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Schema Master" -Value $ADForest.SchemaRoleOwner
3243 $ADForestObj | Add-Member -MemberType NoteProperty -Name "RootDomain" -Value $ADForest.RootDomain
3244 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Domain Count" -Value $ADForest.Domains.Count
3245 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Site Count" -Value $ADForest.Sites.Count
3246 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Global Catalog Count" -Value $ADForest.GlobalCatalogs.Count
3247 For($i=0; $i -lt $ADForest.Domains.Count; $i++)
3248 {
3249 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Domain -$i" -Value $ADForest.Domains[$i]
3250 }
3251 For($i=0; $i -lt $ADForest.Sites.Count; $i++)
3252 {
3253 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Site -$i" -Value $ADForest.Sites[$i]
3254 }
3255 For($i=0; $i -lt $ADForest.GlobalCatalogs.Count; $i++)
3256 {
3257 $ADForestObj | Add-Member -MemberType NoteProperty -Name "GlobalCatalog -$i" -Value $ADForest.GlobalCatalogs[$i]
3258 }
3259 If ($ADRecycleBin)
3260 {
3261 If ($ADRecycleBin.Properties.EnabledScopes.Count -eq 0)
3262 {
3263 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Recycle Bin Enabled" -Value $false
3264 }
3265 Else
3266 {
3267 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Recycle Bin Enabled" -Value $true
3268 }
3269 Remove-Variable ADRecycleBin
3270 }
3271 If ($ADForestTombstoneLifetime)
3272 {
3273 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Tombstone Lifetime" -Value $ADForestTombstoneLifetime
3274 Remove-Variable ADForestTombstoneLifetime
3275 }
3276 Else
3277 {
3278 $ADForestObj | Add-Member -MemberType NoteProperty -Name "Tombstone Lifetime" -Value "Not Retrieved"
3279 }
3280 Remove-Variable ADForest
3281 }
3282 }
3283
3284 If ($ADForestObj)
3285 {
3286 Switch ($OutputType)
3287 {
3288 'STDOUT' { $ADForestObj }
3289 'CSV'
3290 {
3291 Write-Verbose "[+] Forest"
3292 $ADFileName = -join($ReportPath,'\','Forest','.csv')
3293 Try
3294 {
3295 $ADForestObj | Export-Csv -Path $ADFileName -NoTypeInformation
3296 }
3297 Catch
3298 {
3299 Write-Output "[ERROR] Failed to Export CSV File"
3300 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3301 }
3302 Remove-Variable ADForestObj
3303 Remove-Variable ADFileName
3304 }
3305 }
3306 }
3307}
3308
3309Function Get-ADRPassPol
3310{
3311<#
3312.SYNOPSIS
3313 Returns the Default Password Policy of the current (or specified) domain.
3314
3315.DESCRIPTION
3316 Returns the Default Password Policy of the current (or specified) domain.
3317
3318.PARAMETER Protocol
3319 [string]
3320 Which protocol to use; ADWS (default) or LDAP.
3321
3322.PARAMETER UseAltCreds
3323 [bool]
3324 Whether to use provided credentials or not.
3325
3326.PARAMETER ReportPath
3327 [string]
3328 Path for ADRecon output folder.
3329
3330.PARAMETER objDomain
3331 [DirectoryServices.DirectoryEntry]
3332 Domain Directory Entry object.
3333
3334.PARAMETER OutputType
3335 [array]
3336 Output Type.
3337
3338.OUTPUTS
3339 CSV file is created in the folder specified with the information.
3340#>
3341 param(
3342 [Parameter(Mandatory = $true)]
3343 [string] $Protocol,
3344
3345 [Parameter(Mandatory = $true)]
3346 [bool] $UseAltCreds,
3347
3348 [Parameter(Mandatory = $true)]
3349 [string] $ReportPath,
3350
3351 [Parameter(Mandatory = $false)]
3352 [DirectoryServices.DirectoryEntry] $objDomain,
3353
3354 [Parameter(Mandatory = $true)]
3355 [array] $OutputType
3356 )
3357
3358 Write-Output "[-] Default Password Policy"
3359 If ($Protocol -eq 'ADWS')
3360 {
3361 Try
3362 {
3363 $ADpasspolicy = Get-ADDefaultDomainPasswordPolicy
3364 }
3365 Catch
3366 {
3367 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3368 }
3369
3370 If ($ADpasspolicy)
3371 {
3372 $ADPassPolObj = New-Object PSObject
3373 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Policy" -Value "Value"
3374 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Enforce password history" -Value $ADpasspolicy.PasswordHistoryCount
3375 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Maximum password age (days)" -Value $ADpasspolicy.MaxPasswordAge.days
3376 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password age (days)" -Value $ADpasspolicy.MinPasswordAge.days
3377 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password length" -Value $ADpasspolicy.MinPasswordLength
3378 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Password must meet complexity requirements" -Value $ADpasspolicy.ComplexityEnabled
3379 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Store password using reversible encryption for all users in the domain" -Value $ADpasspolicy.ReversibleEncryptionEnabled
3380 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout duration (mins)" -Value $ADpasspolicy.LockoutDuration.minutes
3381 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout threshold" -Value $ADpasspolicy.LockoutThreshold
3382 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Reset account lockout counter after (mins)" -Value $ADpasspolicy.LockoutObservationWindow.minutes
3383 Remove-Variable ADpasspolicy
3384 }
3385
3386 Write-Output "[-] Fine Grained Password Policy - May need a Privileged Account"
3387 Try
3388 {
3389 $ADFinepasspolicy = Get-ADFineGrainedPasswordPolicy -Filter *
3390 }
3391 Catch
3392 {
3393 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3394 }
3395
3396 If ($ADFinepasspolicy)
3397 {
3398 $i = 0
3399 $ADFinepasspolicy | ForEach-Object {
3400 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Name -$i" -Value $($_.Name)
3401 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Applies To -$i" -Value $($_.AppliesTo)
3402 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Enforce password history -$i" -Value $_.PasswordHistoryCount
3403 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Maximum password age (days) -$i" -Value $_.MaxPasswordAge.days
3404 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password age (days) -$i" -Value $_.MinPasswordAge.days
3405 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password length -$i" -Value $_.MinPasswordLength
3406 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Password must meet complexity requirements -$i" -Value $_.ComplexityEnabled
3407 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Store password using reversible encryption -$i" -Value $_.ReversibleEncryptionEnabled
3408 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout duration (mins) -$i" -Value $_.LockoutDuration.minutes
3409 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout threshold -$i" -Value $_.LockoutThreshold
3410 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Reset account lockout counter after (mins) -$i" -Value $_.LockoutObservationWindow.minutes
3411 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Precedence -$i" -Value $($_.Precedence)
3412 $i ++
3413 }
3414 Remove-Variable ADFinepasspolicy
3415 }
3416 }
3417
3418 If ($Protocol -eq 'LDAP')
3419 {
3420 If ($ObjDomain)
3421 {
3422 #Value taken from https://msdn.microsoft.com/en-us/library/ms679431(v=vs.85).aspx
3423 $pwdProperties = @{
3424 "DOMAIN_PASSWORD_COMPLEX" = 1;
3425 "DOMAIN_PASSWORD_NO_ANON_CHANGE" = 2;
3426 "DOMAIN_PASSWORD_NO_CLEAR_CHANGE" = 4;
3427 "DOMAIN_LOCKOUT_ADMINS" = 8;
3428 "DOMAIN_PASSWORD_STORE_CLEARTEXT" = 16;
3429 "DOMAIN_REFUSE_PASSWORD_CHANGE" = 32
3430 }
3431
3432 $ADPassPolObj = New-Object PSObject
3433 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Policy" -Value "Value"
3434 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Enforce password history" -Value $ObjDomain.PwdHistoryLength.value
3435 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Maximum password age (days)" -Value $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.maxpwdage.value) /-864000000000)
3436 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password age (days)" -Value $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.minpwdage.value) /-864000000000)
3437 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password length" -Value $ObjDomain.MinPwdLength.value
3438 If (($ObjDomain.pwdproperties.value -band $pwdProperties["DOMAIN_PASSWORD_COMPLEX"]) -eq $pwdProperties["DOMAIN_PASSWORD_COMPLEX"])
3439 {
3440 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Password must meet complexity requirements" -Value $true
3441 }
3442 Else
3443 {
3444 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Password must meet complexity requirements" -Value $false
3445 }
3446 If (($ObjDomain.pwdproperties.value -band $pwdProperties["DOMAIN_PASSWORD_STORE_CLEARTEXT"]) -eq $pwdProperties["DOMAIN_PASSWORD_STORE_CLEARTEXT"])
3447 {
3448 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Store password using reversible encryption for all users in the domain" -Value $true
3449 }
3450 Else
3451 {
3452 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Store password using reversible encryption for all users in the domain" -Value $false
3453 }
3454 Remove-Variable pwdProperties
3455 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout duration (mins)" -Value $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.lockoutduration.value)/-600000000)
3456 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout threshold" -Value $ObjDomain.LockoutThreshold.value
3457 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Reset account lockout counter after (mins)" -Value $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.lockoutobservationWindow.value)/-600000000)
3458
3459 Write-Output "[-] Fine Grained Password Policy - May need a Privileged Account"
3460 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
3461 $ObjSearcher.PageSize = $PageSize
3462 $ObjSearcher.Filter = "(objectClass=msDS-PasswordSettings)"
3463 #$ObjSearcher.PropertiesToLoad.AddRange(("admincount","canonicalname","description","distinguishedname","lastLogontimestamp","name","objectsid","primarygroupid","pwdLastSet","samaccountName","serviceprincipalname","sidhistory","useraccountcontrol","userworkstations","whenchanged","whencreated"))
3464 $ObjSearcher.SearchScope = "Subtree"
3465 Try
3466 {
3467 $ADFinepasspolicy = $ObjSearcher.FindAll()
3468 }
3469 Catch
3470 {
3471 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3472 Return $null
3473 }
3474
3475 If ($ADFinepasspolicy)
3476 {
3477 $i = 0
3478 $ADFinepasspolicy | ForEach-Object {
3479 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Name -$i" -Value $($_.Properties.name)
3480 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Applies To -$i" -Value $($_.Properties.'msds-psoappliesto')
3481 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Enforce password history -$i" -Value $($_.Properties.'msds-passwordhistorylength')
3482 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Maximum password age (days) -$i" -Value $($($_.Properties.'msds-maximumpasswordage') /-864000000000)
3483 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password age (days) -$i" -Value $($($_.Properties.'msds-minimumpasswordage') /-864000000000)
3484 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Minimum password length -$i" -Value $($_.Properties.'msds-minimumpasswordlength')
3485 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Password must meet complexity requirements -$i" -Value $($_.Properties.'msds-passwordcomplexityenabled')
3486 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Store password using reversible encryption -$i" -Value $($_.Properties.'msds-passwordreversibleencryptionenabled')
3487 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout duration (mins) -$i" -Value $($($_.Properties.'msds-lockoutduration')/-600000000)
3488 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Account lockout threshold -$i" -Value $($_.Properties.'msds-lockoutthreshold')
3489 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Reset account lockout counter after (mins) -$i" -Value $($($_.Properties.'msds-lockoutobservationwindow')/-600000000)
3490 $ADPassPolObj | Add-Member -MemberType NoteProperty -Name "Precedence -$i" -Value $($_.Properties.'msds-passwordsettingsprecedence')
3491 $i ++
3492 }
3493 Remove-Variable ADFinepasspolicy
3494 }
3495 }
3496 }
3497
3498 If ($ADPassPolObj)
3499 {
3500 Switch ($OutputType)
3501 {
3502 'STDOUT' { $ADPassPolObj }
3503 'CSV'
3504 {
3505 Write-Verbose "[+] Default Password Policy"
3506 $ADFileName = -join($ReportPath,'\','DefaultPasswordPolicy','.csv')
3507 Try
3508 {
3509 $ADPassPolObj | Export-Csv -Path $ADFileName -NoTypeInformation
3510 }
3511 Catch
3512 {
3513 Write-Output "[ERROR] Failed to Export CSV File"
3514 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3515 }
3516 Remove-Variable ADPassPolObj
3517 Remove-Variable ADFileName
3518 }
3519 }
3520 }
3521}
3522
3523Function Get-ADRDC
3524{
3525<#
3526.SYNOPSIS
3527 Returns the domain controllers for the current (or specified) forest.
3528
3529.DESCRIPTION
3530 Returns the domain controllers for the current (or specified) forest.
3531
3532.PARAMETER Protocol
3533 [string]
3534 Which protocol to use; ADWS (default) or LDAP.
3535
3536.PARAMETER UseAltCreds
3537 [bool]
3538 Whether to use provided credentials or not.
3539
3540.PARAMETER ReportPath
3541 [string]
3542 Path for ADRecon output folder.
3543
3544.PARAMETER objDomain
3545 [DirectoryServices.DirectoryEntry]
3546 Domain Directory Entry object.
3547
3548.PARAMETER OutputType
3549 [array]
3550 Output Type.
3551
3552.OUTPUTS
3553 CSV file is created in the folder specified with the information.
3554#>
3555 param(
3556 [Parameter(Mandatory = $true)]
3557 [string] $Protocol,
3558
3559 [Parameter(Mandatory = $true)]
3560 [bool] $UseAltCreds,
3561
3562 [Parameter(Mandatory = $true)]
3563 [string] $ReportPath,
3564
3565 [Parameter(Mandatory = $false)]
3566 [DirectoryServices.DirectoryEntry] $objDomain,
3567
3568 [Parameter(Mandatory = $true)]
3569 [array] $OutputType
3570 )
3571
3572 Write-Output "[-] Domain Controllers"
3573 If ($Protocol -eq 'ADWS')
3574 {
3575 Try
3576 {
3577 $allDCs = Get-ADDomainController -Filter *
3578 }
3579 Catch
3580 {
3581 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3582 }
3583
3584 # DC Info
3585 If ($allDCs)
3586 {
3587 # DC Info
3588 $DCObj = @()
3589 $allDCs | ForEach-Object {
3590 # Create the object for each instance.
3591 $Obj = New-Object PSObject
3592 $Obj | Add-Member -MemberType NoteProperty -Name "Domain" -Value $_.Domain
3593 $Obj | Add-Member -MemberType NoteProperty -Name "Site" -Value $_.Site
3594 $Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
3595 $Obj | Add-Member -MemberType NoteProperty -Name "IPv4Address" -Value $_.IPv4Address
3596 $OSVersion = $_.OperatingSystem + $_.OperatingSystemHotfix + $_.OperatingSystemServicePack + $_.OperatingSystemVersion
3597 $Obj | Add-Member -MemberType NoteProperty -Name "Operating System" -Value $OSVersion
3598 Remove-Variable OSVersion
3599 $Obj | Add-Member -MemberType NoteProperty -Name "Hostname" -Value $_.HostName
3600 If ($_.OperationMasterRoles -like 'InfrastructureMaster')
3601 {
3602 $Obj | Add-Member -MemberType NoteProperty -Name "Infra" -Value $true
3603 }
3604 Else
3605 {
3606 $Obj | Add-Member -MemberType NoteProperty -Name "Infra" -Value $false
3607 }
3608 If ($_.OperationMasterRoles -like 'DomainNamingMaster')
3609 {
3610 $Obj | Add-Member -MemberType NoteProperty -Name "Naming" -Value $true
3611 }
3612 Else
3613 {
3614 $Obj | Add-Member -MemberType NoteProperty -Name "Naming" -Value $false
3615 }
3616 If ($_.OperationMasterRoles -like 'SchemaMaster')
3617 {
3618 $Obj | Add-Member -MemberType NoteProperty -Name "Schema" -Value $true
3619 }
3620 Else
3621 {
3622 $Obj | Add-Member -MemberType NoteProperty -Name "Schema" -Value $false
3623 }
3624 If ($_.OperationMasterRoles -like 'RIDMaster')
3625 {
3626 $Obj | Add-Member -MemberType NoteProperty -Name "RID" -Value $true
3627 }
3628 Else
3629 {
3630 $Obj | Add-Member -MemberType NoteProperty -Name "RID" -Value $false
3631 }
3632 If ($_.OperationMasterRoles -like 'PDCEmulator')
3633 {
3634 $Obj | Add-Member -MemberType NoteProperty -Name "PDC" -Value $true
3635 }
3636 Else
3637 {
3638 $Obj | Add-Member -MemberType NoteProperty -Name "PDC" -Value $false
3639 }
3640 $DCObj += $Obj
3641 }
3642 Remove-Variable allDCs
3643 }
3644 }
3645
3646 If ($Protocol -eq 'LDAP')
3647 {
3648 If ($UseAltCreds)
3649 {
3650 $DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
3651 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($creds.UserName),$($creds.GetNetworkCredential().password))
3652 Try
3653 {
3654 $ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
3655 }
3656 Catch
3657 {
3658 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3659 Return $null
3660 }
3661 Remove-Variable DomainContext
3662 }
3663 Else
3664 {
3665 $ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
3666 }
3667
3668 If ($ADDomain.DomainControllers)
3669 {
3670 # DC Info
3671 $DCObj = @()
3672 $ADDomain.DomainControllers | ForEach-Object {
3673 # Create the object for each instance.
3674 $Obj = New-Object PSObject
3675 $Obj | Add-Member -MemberType NoteProperty -Name "Domain" -Value $_.Domain
3676 $Obj | Add-Member -MemberType NoteProperty -Name "Site" -Value $_.SiteName
3677 $Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
3678 $Obj | Add-Member -MemberType NoteProperty -Name "IPAddress" -Value $_.IPAddress
3679 $Obj | Add-Member -MemberType NoteProperty -Name "Operating System" -Value $_.OSVersion
3680 $Obj | Add-Member -MemberType NoteProperty -Name "Hostname" -Value $_.Hostname
3681 If ($null -ne $_.Roles)
3682 {
3683 $Obj | Add-Member -MemberType NoteProperty -Name "Infra" -Value $($_.Roles.Contains("InfrastructureRole"))
3684 $Obj | Add-Member -MemberType NoteProperty -Name "Naming" -Value $($_.Roles.Contains("NamingRole"))
3685 $Obj | Add-Member -MemberType NoteProperty -Name "Schema" -Value $($_.Roles.Contains("SchemaRole"))
3686 $Obj | Add-Member -MemberType NoteProperty -Name "RID" -Value $($_.Roles.Contains("RidRole"))
3687 $Obj | Add-Member -MemberType NoteProperty -Name "PDC" -Value $($_.Roles.Contains("PdcRole"))
3688 }
3689 Else
3690 {
3691 $Obj | Add-Member -MemberType NoteProperty -Name "Infra" -Value $false
3692 $Obj | Add-Member -MemberType NoteProperty -Name "Naming" -Value $false
3693 $Obj | Add-Member -MemberType NoteProperty -Name "Schema" -Value $false
3694 $Obj | Add-Member -MemberType NoteProperty -Name "RID" -Value $false
3695 $Obj | Add-Member -MemberType NoteProperty -Name "PDC" -Value $false
3696 }
3697 $DCObj += $Obj
3698 }
3699 Remove-Variable ADDomain
3700 }
3701 }
3702
3703 If ($DCObj)
3704 {
3705 Switch ($OutputType)
3706 {
3707 'STDOUT' { $DCObj }
3708 'CSV'
3709 {
3710 Write-Verbose "[+] Domain Controllers"
3711 $ADFileName = -join($ReportPath,'\','DCs','.csv')
3712 Try
3713 {
3714 $DCObj | Export-Csv -Path $ADFileName -NoTypeInformation
3715 }
3716 Catch
3717 {
3718 Write-Output "[ERROR] Failed to Export CSV File"
3719 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3720 }
3721 Remove-Variable DCObj
3722 Remove-Variable ADFileName
3723 }
3724 }
3725 }
3726}
3727
3728Function Get-ADRUser
3729{
3730<#
3731.SYNOPSIS
3732 Returns all users in the current (or specified) domain.
3733
3734.DESCRIPTION
3735 Returns all users in the current (or specified) domain.
3736
3737.PARAMETER Protocol
3738 [string]
3739 Which protocol to use; ADWS (default) or LDAP.
3740
3741.PARAMETER UseAltCreds
3742 [bool]
3743 Whether to use provided credentials or not.
3744
3745.PARAMETER ReportPath
3746 [string]
3747 Path for ADRecon output folder.
3748
3749.PARAMETER date
3750 [DateTime]
3751 Date when ADRecon was executed.
3752
3753.PARAMETER objDomain
3754 [DirectoryServices.DirectoryEntry]
3755 Domain Directory Entry object.
3756
3757.PARAMETER DormantTimeSpan
3758 [int]
3759 Timespan for Dormant accounts. Default 90 days.
3760
3761.PARAMETER PageSize
3762 [int]
3763 The PageSize to set for the LDAP searcher object. Default 200.
3764
3765.PARAMETER Threads
3766 [int]
3767 The number of threads to use during processing of objects. Default 10.
3768
3769.PARAMETER FlushCount
3770 [int]
3771 The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).
3772
3773.PARAMETER OutputType
3774 [array]
3775 Output Type.
3776
3777.OUTPUTS
3778 CSV file is created in the folder specified with the information.
3779#>
3780 param(
3781 [Parameter(Mandatory = $true)]
3782 [string] $Protocol,
3783
3784 [Parameter(Mandatory = $true)]
3785 [bool] $UseAltCreds,
3786
3787 [Parameter(Mandatory = $true)]
3788 [string] $ReportPath,
3789
3790 [Parameter(Mandatory = $true)]
3791 [DateTime] $date,
3792
3793 [Parameter(Mandatory = $false)]
3794 [DirectoryServices.DirectoryEntry] $objDomain,
3795
3796 [Parameter(Mandatory = $true)]
3797 [int] $DormantTimeSpan = 90,
3798
3799 [Parameter(Mandatory = $true)]
3800 [int] $PageSize,
3801
3802 [Parameter(Mandatory = $false)]
3803 [int] $Threads = 10,
3804
3805 [Parameter(Mandatory = $false)]
3806 [int] $FlushCount = -1,
3807
3808 [Parameter(Mandatory = $true)]
3809 [array] $OutputType
3810 )
3811
3812 Write-Output "[-] Domain Users - May take some time"
3813 If ($Protocol -eq 'ADWS')
3814 {
3815 Try
3816 {
3817 $ADUsers = Get-ADUser -Filter * -ResultPageSize $PageSize -Properties AdminCount,AllowReversiblePasswordEncryption,CannotChangePassword,CanonicalName,Description,DistinguishedName,DoesNotRequirePreAuth,Enabled,LastLogonDate,LockedOut,LogonWorkstations,Name,PasswordLastSet,PasswordNeverExpires,PasswordNotRequired,primaryGroupID,pwdlastset,SamAccountName,SID,SIDHistory,TrustedForDelegation,TrustedToAuthForDelegation,whenChanged,whenCreated
3818 }
3819 Catch
3820 {
3821 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3822 Continue
3823 }
3824
3825 If ($ADUsers)
3826 {
3827 Try
3828 {
3829 $ADpasspolicy = Get-ADDefaultDomainPasswordPolicy
3830 $PassMaxAge = $ADpasspolicy.MaxPasswordAge.days
3831 Remove-Variable ADpasspolicy
3832 }
3833 Catch
3834 {
3835 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3836 $PassMaxAge = 90
3837 }
3838
3839 $ADFileName = -join($ReportPath,'\','Users','.csv')
3840 [ADRecon.ADWSClass]::UserParser($ADUsers, $date, $PassMaxAge, $ADFileName, $DormantTimeSpan, $Threads, $FlushCount, $OutputType)
3841 Remove-Variable ADUsers
3842 Write-Verbose "[+] Domain Users"
3843 }
3844 Write-Verbose "[+] Domain Users"
3845 }
3846
3847 If ($Protocol -eq 'LDAP')
3848 {
3849 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
3850 $ObjSearcher.PageSize = $PageSize
3851 $ObjSearcher.Filter = "(samAccountType=805306368)"
3852 $ObjSearcher.PropertiesToLoad.AddRange(("admincount","canonicalname","description","distinguishedname","lastLogontimestamp","name","objectsid","primarygroupid","pwdLastSet","samaccountName","serviceprincipalname","sidhistory","useraccountcontrol","userworkstations","whenchanged","whencreated"))
3853 $ObjSearcher.SearchScope = "Subtree"
3854 Try
3855 {
3856 $ADUsers = $ObjSearcher.FindAll()
3857 }
3858 Catch
3859 {
3860 Write-Output "[EXCEPTION] $($_.Exception.Message)"
3861 Return $null
3862 }
3863 $ObjSearcher.dispose()
3864
3865 If ($ADUsers)
3866 {
3867 $PassMaxAge = $($ObjDomain.ConvertLargeIntegerToInt64($ObjDomain.maxpwdage.value) /-864000000000)
3868 If (-Not $PassMaxAge)
3869 {
3870 $PassMaxAge = 90
3871 }
3872 $icnt = 1
3873 $cnt = $($ADUsers | Measure-Object | Select-Object -ExpandProperty Count)
3874 Write-Output "[*] Calculating if the user Cannot Change Password"
3875 $CannotChangePassword = New-Object 'System.Collections.Generic.Dictionary[String,bool]'
3876 $StopWatch = [System.Diagnostics.StopWatch]::StartNew()
3877 $ADUsers | ForEach-Object {
3878 If ($StopWatch.Elapsed.TotalMilliseconds -ge 1000)
3879 {
3880 Write-Progress -Activity "Calculating if the user Cannot Change Password" -Status "$("{0:N2}" -f (($icnt/$cnt*100),2)) % Complete:" -PercentComplete 100
3881 $StopWatch.Reset()
3882 $StopWatch.Start()
3883 }
3884 # Get ACLs to determine if the user can change their password or not
3885 $data = $_.GetDirectoryEntry()
3886 $aclObject = $data.Get_ObjectSecurity()
3887 ForEach ($access in $aclObject.Access)
3888 {
3889 If (($access.ObjectType -eq "ab721a53-1e2f-11d0-9819-00aa0040529b") -or ($access.ObjectType -eq "AB721A53-1E2F-11D0-9819-00AA0040529B"))
3890 {
3891 If ($access.AccessControlType -eq "Deny")
3892 {
3893 If ($access.IdentityReference -eq "Everyone")
3894 {
3895 $DenyEveryone = $true
3896 }
3897 Elseif ($access.IdentityReference -eq "NT AUTHORITY\SELF")
3898 {
3899 $DenySelf = $true
3900 }
3901 }
3902 }
3903 }
3904 If ($DenyEveryone -and $DenySelf)
3905 {
3906 $CannotChangePassword.Add($($_.properties.samaccountname),$true)
3907 Remove-Variable DenyEveryone
3908 Remove-Variable DenySelf
3909 }
3910 Else
3911 {
3912 $CannotChangePassword.Add($($_.properties.samaccountname),$false)
3913 }
3914 Remove-Variable data
3915 Remove-Variable aclObject
3916 $icnt ++
3917 }
3918 Write-Progress -Activity "Calculating if the user Cannot Change Password" -Completed -Status "All Done"
3919 $ADFileName = -join($ReportPath,'\','Users','.csv')
3920 [ADRecon.LDAPClass]::UserParser($ADUsers, $date, $PassMaxAge, $ADFileName, $CannotChangePassword, $DormantTimeSpan, $Threads, $FlushCount, $OutputType)
3921 Remove-Variable ADUsers
3922 Write-Verbose "[+] Domain Users"
3923 }
3924 }
3925}
3926
3927Function Get-ADRUserSPN
3928{
3929<#
3930.SYNOPSIS
3931 Returns all user service principal name (SPN) in the current (or specified) domain.
3932
3933.DESCRIPTION
3934 Returns all user service principal name (SPN) in the current (or specified) domain.
3935
3936.PARAMETER Protocol
3937 [string]
3938 Which protocol to use; ADWS (default) or LDAP.
3939
3940.PARAMETER UseAltCreds
3941 [bool]
3942 Whether to use provided credentials or not.
3943
3944.PARAMETER ReportPath
3945 [string]
3946 Path for ADRecon output folder.
3947
3948.PARAMETER objDomain
3949 [DirectoryServices.DirectoryEntry]
3950 Domain Directory Entry object.
3951
3952.PARAMETER PageSize
3953 [int]
3954 The PageSize to set for the LDAP searcher object. Default 200.
3955
3956.PARAMETER Threads
3957 [int]
3958 The number of threads to use during processing of objects. Default 10.
3959
3960.PARAMETER FlushCount
3961 [int]
3962 The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).
3963
3964.PARAMETER OutputType
3965 [array]
3966 Output Type.
3967
3968.OUTPUTS
3969 CSV file is created in the folder specified with the information.
3970#>
3971 param(
3972 [Parameter(Mandatory = $true)]
3973 [string] $Protocol,
3974
3975 [Parameter(Mandatory = $true)]
3976 [bool] $UseAltCreds,
3977
3978 [Parameter(Mandatory = $true)]
3979 [string] $ReportPath,
3980
3981 [Parameter(Mandatory = $false)]
3982 [DirectoryServices.DirectoryEntry] $objDomain,
3983
3984 [Parameter(Mandatory = $true)]
3985 [int] $PageSize,
3986
3987 [Parameter(Mandatory = $false)]
3988 [int] $Threads = 10,
3989
3990 [Parameter(Mandatory = $false)]
3991 [int] $FlushCount = -1,
3992
3993 [Parameter(Mandatory = $true)]
3994 [array] $OutputType
3995 )
3996
3997 Write-Output "[-] Domain User SPNs"
3998 If ($Protocol -eq 'ADWS')
3999 {
4000 Try
4001 {
4002 $ADUsers = Get-ADObject -LDAPFilter "(&(!objectClass=computer)(servicePrincipalName=*))" -Properties Name,sAMAccountName,servicePrincipalName,pwdLastSet,Description -ResultPageSize $PageSize
4003 }
4004 Catch
4005 {
4006 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4007 Continue
4008 }
4009
4010 If ($ADUsers)
4011 {
4012 $ADFileName = -join($ReportPath,'\','UserSPNs','.csv')
4013 $SPNCount = [ADRecon.ADWSClass]::UserSPNParser($ADUsers, $ADFileName, $Threads, $FlushCount, $OutputType)
4014 # Temporary solution for exception in [ADRecon.ADWSClass]::UserSPNParser
4015 # System.InvalidCastException: Unable to cast object of type 'Microsoft.ActiveDirectory.Management.ADObject' to type 'System.Management.Automation.PSObject'.
4016 If ($SPNCount -eq 1)
4017 {
4018 $UserSPNObj = @()
4019 $ADUsers | ForEach-Object {
4020 For($i=0; $i -lt $_.servicePrincipalName.count; $i++)
4021 {
4022 $Obj = New-Object PSObject
4023 [array] $SPNObjectArray = $_.servicePrincipalName[$i] -Split("/")
4024 $Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
4025 $Obj | Add-Member -MemberType NoteProperty -Name "Username" -Value $_.sAMAccountName
4026 $Obj | Add-Member -MemberType NoteProperty -Name "Service" -Value $SPNObjectArray[0]
4027 $Obj | Add-Member -MemberType NoteProperty -Name "Host" -Value $SPNObjectArray[1]
4028 If ($null -ne $_.pwdLastSet)
4029 {
4030 $pwdlastSet = [datetime]::FromFileTime($_.pwdLastSet)
4031 }
4032 Else
4033 {
4034 $pwdlastSet = "-"
4035 }
4036 $Obj | Add-Member -MemberType NoteProperty -Name "Password Last Set" -Value $pwdlastSet
4037 $Obj | Add-Member -MemberType NoteProperty -Name "Description" -Value $_.description
4038 $UserSPNObj += $Obj
4039 }
4040 }
4041 If ($UserSPNObj)
4042 {
4043 Switch ($OutputType)
4044 {
4045 'STDOUT' { $UserSPNObj }
4046 'CSV'
4047 {
4048 Try
4049 {
4050 $UserSPNObj | Export-Csv -Path $ADFileName -NoTypeInformation
4051 }
4052 Catch
4053 {
4054 Write-Output "[ERROR] Failed to Export CSV File"
4055 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4056 }
4057 }
4058 }
4059 }
4060 }
4061 Remove-Variable ADUsers
4062 Write-Verbose "[+] Domain User SPNs"
4063 }
4064 }
4065
4066 If ($Protocol -eq 'LDAP')
4067 {
4068 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
4069 $ObjSearcher.PageSize = $PageSize
4070 $ObjSearcher.Filter = "(&(!objectClass=computer)(servicePrincipalName=*))"
4071 $ObjSearcher.PropertiesToLoad.AddRange(("name","samaccountname","serviceprincipalname","pwdlastset","description"))
4072 $ObjSearcher.SearchScope = "Subtree"
4073 Try
4074 {
4075 $ADUsers = $ObjSearcher.FindAll()
4076 }
4077 Catch
4078 {
4079 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4080 Return $null
4081 }
4082 $ObjSearcher.dispose()
4083
4084 If ($ADUsers)
4085 {
4086 $ADFileName = -join($ReportPath,'\','UserSPNs','.csv')
4087 [ADRecon.LDAPClass]::UserSPNParser($ADUsers, $ADFileName, $Threads, $FlushCount, $OutputType)
4088 Remove-Variable ADUsers
4089 Write-Verbose "[+] Domain User SPNs"
4090 }
4091 }
4092}
4093
4094Function Get-ADRGroup
4095{
4096<#
4097.SYNOPSIS
4098 Returns all groups in the current (or specified) domain.
4099
4100.DESCRIPTION
4101 Returns all groups in the current (or specified) domain.
4102
4103.PARAMETER Protocol
4104 [string]
4105 Which protocol to use; ADWS (default) or LDAP.
4106
4107.PARAMETER UseAltCreds
4108 [bool]
4109 Whether to use provided credentials or not.
4110
4111.PARAMETER ReportPath
4112 [string]
4113 Path for ADRecon output folder.
4114
4115.PARAMETER objDomain
4116 [DirectoryServices.DirectoryEntry]
4117 Domain Directory Entry object.
4118
4119.PARAMETER PageSize
4120 [int]
4121 The PageSize to set for the LDAP searcher object. Default 200.
4122
4123.PARAMETER Threads
4124 [int]
4125 The number of threads to use during processing of objects. Default 10.
4126
4127.PARAMETER FlushCount
4128 [int]
4129 The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).
4130
4131.PARAMETER OutputType
4132 [array]
4133 Output Type.
4134
4135.OUTPUTS
4136 CSV file is created in the folder specified with the information.
4137#>
4138 param(
4139 [Parameter(Mandatory = $true)]
4140 [string] $Protocol,
4141
4142 [Parameter(Mandatory = $true)]
4143 [bool] $UseAltCreds,
4144
4145 [Parameter(Mandatory = $true)]
4146 [string] $ReportPath,
4147
4148 [Parameter(Mandatory = $false)]
4149 [DirectoryServices.DirectoryEntry] $objDomain,
4150
4151 [Parameter(Mandatory = $true)]
4152 [int] $PageSize,
4153
4154 [Parameter(Mandatory = $false)]
4155 [int] $Threads = 10,
4156
4157 [Parameter(Mandatory = $false)]
4158 [int] $FlushCount = -1,
4159
4160 [Parameter(Mandatory = $true)]
4161 [array] $OutputType
4162 )
4163
4164 Write-Output "[-] Domain Groups - May take some time"
4165 If ($Protocol -eq 'ADWS')
4166 {
4167 Try
4168 {
4169 $ADGroups = Get-ADGroup -Filter * -ResultPageSize $PageSize -Properties CanonicalName,DistinguishedName,Description,SamAccountName,SID,managedBy,whenChanged,whenCreated
4170 }
4171 Catch
4172 {
4173 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4174 }
4175
4176 If ($ADGroups)
4177 {
4178 $ADFileName = -join($ReportPath,'\','Groups','.csv')
4179 [ADRecon.ADWSClass]::GroupParser($ADGroups, $ADFileName, $Threads, $FlushCount, $OutputType)
4180 Remove-Variable ADGroups
4181 Write-Verbose "[+] Domain Groups"
4182 }
4183 }
4184
4185 If ($Protocol -eq 'LDAP')
4186 {
4187 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
4188 $ObjSearcher.PageSize = $PageSize
4189 $ObjSearcher.Filter = "(objectClass=group)"
4190 $ObjSearcher.PropertiesToLoad.AddRange(("canonicalname", "distinguishedname", "description", "samaccountname", "managedby", "objectsid", "whencreated", "whenchanged"))
4191 $ObjSearcher.SearchScope = "Subtree"
4192
4193 Try
4194 {
4195 $ADGroups = $ObjSearcher.FindAll()
4196 }
4197 Catch
4198 {
4199 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4200 }
4201 $ObjSearcher.dispose()
4202
4203 If ($ADGroups)
4204 {
4205 $ADFileName = -join($ReportPath,'\','Groups','.csv')
4206 [ADRecon.LDAPClass]::GroupParser($ADGroups, $ADFileName, $Threads, $FlushCount, $OutputType)
4207 Remove-Variable ADGroups
4208 Write-Verbose "[+] Domain Groups"
4209 }
4210 }
4211}
4212
4213Function Get-ADRGroupMember
4214{
4215<#
4216.SYNOPSIS
4217 Returns all groups and their members in the current (or specified) domain.
4218
4219.DESCRIPTION
4220 Returns all groups and their members in the current (or specified) domain.
4221
4222.PARAMETER Protocol
4223 [string]
4224 Which protocol to use; ADWS (default) or LDAP.
4225
4226.PARAMETER UseAltCreds
4227 [bool]
4228 Whether to use provided credentials or not.
4229
4230.PARAMETER ReportPath
4231 [string]
4232 Path for ADRecon output folder.
4233
4234.PARAMETER objDomain
4235 [DirectoryServices.DirectoryEntry]
4236 Domain Directory Entry object.
4237
4238.PARAMETER PageSize
4239 [int]
4240 The PageSize to set for the LDAP searcher object. Default 200.
4241
4242.PARAMETER Threads
4243 [int]
4244 The number of threads to use during processing of objects. Default 10.
4245
4246.PARAMETER FlushCount
4247 [int]
4248 The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).
4249
4250.PARAMETER OutputType
4251 [array]
4252 Output Type.
4253
4254.OUTPUTS
4255 CSV file is created in the folder specified with the information.
4256#>
4257 param(
4258 [Parameter(Mandatory = $true)]
4259 [string] $Protocol,
4260
4261 [Parameter(Mandatory = $true)]
4262 [bool] $UseAltCreds,
4263
4264 [Parameter(Mandatory = $true)]
4265 [string] $ReportPath,
4266
4267 [Parameter(Mandatory = $false)]
4268 [DirectoryServices.DirectoryEntry] $objDomain,
4269
4270 [Parameter(Mandatory = $true)]
4271 [int] $PageSize,
4272
4273 [Parameter(Mandatory = $false)]
4274 [int] $Threads = 10,
4275
4276 [Parameter(Mandatory = $false)]
4277 [int] $FlushCount = -1,
4278
4279 [Parameter(Mandatory = $true)]
4280 [array] $OutputType
4281 )
4282
4283 Write-Output "[-] Domain Group Memberships - May take some time"
4284 If ($Protocol -eq 'ADWS')
4285 {
4286 Try
4287 {
4288 $ADGroups = Get-ADObject -LDAPFilter '(memberof=*)' -Properties DistinguishedName,sAMAccountName,memberof,samaccounttype
4289 }
4290 Catch
4291 {
4292 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4293 }
4294
4295 If ($ADGroups)
4296 {
4297 $ADFileName = -join($ReportPath,'\','GroupMembers','.csv')
4298 [ADRecon.ADWSClass]::GroupMemberParser($ADGroups, $ADFileName, $Threads, $FlushCount, $OutputType)
4299 Remove-Variable ADGroups
4300 Write-Verbose "[+] Domain Group Memberships"
4301 }
4302 }
4303
4304 If ($Protocol -eq 'LDAP')
4305 {
4306 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
4307 $ObjSearcher.PageSize = $PageSize
4308 $ObjSearcher.Filter = "(memberof=*)"
4309 $ObjSearcher.PropertiesToLoad.AddRange(("samaccountname", "distinguishedname", "dnshostname", "samaccounttype", "memberof"))
4310 $ObjSearcher.SearchScope = "Subtree"
4311
4312 Try
4313 {
4314 $ADGroups = $ObjSearcher.FindAll()
4315 }
4316 Catch
4317 {
4318 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4319 }
4320 $ObjSearcher.dispose()
4321
4322 If ($ADGroups)
4323 {
4324 $ADFileName = -join($ReportPath,'\','GroupMembers','.csv')
4325 [ADRecon.LDAPClass]::GroupMemberParser($ADGroups, $ADFileName, $Threads, $FlushCount, $OutputType)
4326 Remove-Variable ADGroups
4327 Write-Verbose "[+] Domain Group Memberships"
4328 }
4329 }
4330}
4331
4332Function Get-ADROU
4333{
4334<#
4335.SYNOPSIS
4336 Returns all Organizational Units (OU) in the current (or specified) domain.
4337
4338.DESCRIPTION
4339 Returns all Organizational Units (OU) in the current (or specified) domain.
4340
4341.PARAMETER Protocol
4342 [string]
4343 Which protocol to use; ADWS (default) or LDAP.
4344
4345.PARAMETER UseAltCreds
4346 [bool]
4347 Whether to use provided credentials or not.
4348
4349.PARAMETER ReportPath
4350 [string]
4351 Path for ADRecon output folder.
4352
4353.PARAMETER objDomain
4354 [DirectoryServices.DirectoryEntry]
4355 Domain Directory Entry object.
4356
4357.PARAMETER PageSize
4358 [int]
4359 The PageSize to set for the LDAP searcher object. Default 200.
4360
4361.PARAMETER OutputType
4362 [array]
4363 Output Type.
4364
4365.OUTPUTS
4366 CSV file is created in the folder specified with the information.
4367#>
4368 param(
4369 [Parameter(Mandatory = $true)]
4370 [string] $Protocol,
4371
4372 [Parameter(Mandatory = $true)]
4373 [bool] $UseAltCreds,
4374
4375 [Parameter(Mandatory = $true)]
4376 [string] $ReportPath,
4377
4378 [Parameter(Mandatory = $false)]
4379 [DirectoryServices.DirectoryEntry] $objDomain,
4380
4381 [Parameter(Mandatory = $true)]
4382 [int] $PageSize,
4383
4384 [Parameter(Mandatory = $true)]
4385 [array] $OutputType
4386 )
4387
4388 Write-Output "[-] Domain OrganizationalUnits"
4389 If ($Protocol -eq 'ADWS')
4390 {
4391 Try
4392 {
4393 $ADOUs = Get-ADOrganizationalUnit -Filter * -Properties Created,DistinguishedName,Description,Name,gPLink,gPOptions
4394 }
4395 Catch
4396 {
4397 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4398 }
4399
4400 If ($ADOUs)
4401 {
4402 Write-Output "[*] Total OUs: $($ADOUs | Measure-Object | Select-Object -ExpandProperty Count)"
4403 $OUObj = @()
4404 $ADOUs | ForEach-Object {
4405 # Create the object for each instance.
4406 $Obj = New-Object PSObject
4407 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value $_.Name
4408 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value $_.Created
4409 $Obj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value $_.DistinguishedName
4410 $Obj | Add-Member -MemberType NoteProperty -Name Description -Value $_.Description
4411 $Obj | Add-Member -MemberType NoteProperty -Name gPLink -Value $_.gPLink
4412 $Obj | Add-Member -MemberType NoteProperty -Name gPOptions -Value $_.gPOptions
4413 $Obj | Add-Member -MemberType NoteProperty -Name Depth -Value $(($_.DistinguishedName -split 'OU=').Count -1)
4414 $OUObj += $Obj
4415 }
4416 Remove-Variable ADOUs
4417 }
4418 }
4419
4420 If ($Protocol -eq 'LDAP')
4421 {
4422 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
4423 $ObjSearcher.PageSize = $PageSize
4424 $ObjSearcher.Filter = "(objectCategory=organizationalunit)"
4425 $ObjSearcher.PropertiesToLoad.AddRange(("whencreated","distinguishedname","description","name","gplink","gpoptions"))
4426 $ObjSearcher.SearchScope = "Subtree"
4427
4428 Try
4429 {
4430 $ADOUs = $ObjSearcher.FindAll()
4431 }
4432 Catch
4433 {
4434 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4435 }
4436 $ObjSearcher.dispose()
4437
4438 If ($ADOUs)
4439 {
4440 Write-Output "[*] Total OUs: $($ADOUs | Measure-Object | Select-Object -ExpandProperty Count)"
4441 $OUObj = @()
4442 $ADOUs | ForEach-Object {
4443 # Create the object for each instance.
4444 $Obj = New-Object PSObject
4445 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value ([string] $($_.Properties.name))
4446 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value ([DateTime] $($_.Properties.whencreated))
4447 $Obj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value ([string] $($_.Properties.distinguishedname))
4448 $Obj | Add-Member -MemberType NoteProperty -Name Description -Value ([string] $($_.Properties.description))
4449 $Obj | Add-Member -MemberType NoteProperty -Name gPLink -Value ([string] $($_.Properties.gplink))
4450 $Obj | Add-Member -MemberType NoteProperty -Name gPOptions -Value ([string] $($_.Properties.gpoptions))
4451 $Obj | Add-Member -MemberType NoteProperty -Name Depth -Value $(($_.Properties.distinguishedname -split 'OU=').Count -1)
4452 $OUObj += $Obj
4453 }
4454 Remove-Variable ADOUs
4455 }
4456 }
4457
4458 If ($OUObj)
4459 {
4460 Switch ($OutputType)
4461 {
4462 'STDOUT' { $OUObj }
4463 'CSV'
4464 {
4465 Write-Verbose "[+] Domain OrganizationalUnits"
4466 $ADFileName = -join($ReportPath,'\','OUs','.csv')
4467 Try
4468 {
4469 $OUObj | Export-Csv -Path $ADFileName -NoTypeInformation
4470 }
4471 Catch
4472 {
4473 Write-Output "Failed to Export CSV File"
4474 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4475 }
4476 Remove-Variable OUObj
4477 Remove-Variable ADFileName
4478 }
4479 }
4480 }
4481}
4482
4483Function Get-ADROUPermission
4484{
4485<#
4486.SYNOPSIS
4487 Returns all Organizational Units (OU) permissions in the current (or specified) domain.
4488
4489.DESCRIPTION
4490 Returns all Organizational Units (OU) permissions in the current (or specified) domain.
4491
4492.PARAMETER Protocol
4493 [string]
4494 Which protocol to use; ADWS (default) or LDAP.
4495
4496.PARAMETER UseAltCreds
4497 [bool]
4498 Whether to use provided credentials or not.
4499
4500.PARAMETER ReportPath
4501 [string]
4502 Path for ADRecon output folder.
4503
4504.PARAMETER objDomain
4505 [DirectoryServices.DirectoryEntry]
4506 Domain Directory Entry object.
4507
4508.PARAMETER DCIP
4509 [string]
4510 IP Address of the Domain Controller.
4511
4512.PARAMETER creds
4513 [Management.Automation.PSCredential]
4514 Credentials.
4515
4516.PARAMETER PageSize
4517 [int]
4518 The PageSize to set for the LDAP searcher object. Default 200.
4519
4520.OUTPUTS
4521 CSV file is created in the folder specified with the information.
4522
4523.PARAMETER OutputType
4524 [array]
4525 Output Type.
4526
4527.LINK
4528 https://gallery.technet.microsoft.com/Active-Directory-OU-1d09f989
4529#>
4530 param(
4531 [Parameter(Mandatory = $true)]
4532 [string] $Protocol,
4533
4534 [Parameter(Mandatory = $true)]
4535 [bool] $UseAltCreds,
4536
4537 [Parameter(Mandatory = $true)]
4538 [string] $ReportPath,
4539
4540 [Parameter(Mandatory = $false)]
4541 [DirectoryServices.DirectoryEntry] $objDomain,
4542
4543 [Parameter(Mandatory = $false)]
4544 [string] $DCIP,
4545
4546 [Parameter(Mandatory = $false)]
4547 [Management.Automation.PSCredential] $creds = [Management.Automation.PSCredential]::Empty,
4548
4549 [Parameter(Mandatory = $true)]
4550 [int] $PageSize,
4551
4552 [Parameter(Mandatory = $true)]
4553 [array] $OutputType
4554 )
4555
4556 Write-Output "[-] Domain OrganizationalUnits Permissions - May take some time"
4557 # based on https://gallery.technet.microsoft.com/Active-Directory-OU-1d09f989
4558 If ($Protocol -eq 'ADWS')
4559 {
4560 Try
4561 {
4562 If (-Not $UseAltCreds)
4563 {
4564 Set-Location AD:
4565 }
4566 $schemaIDGUID = @{}
4567 $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
4568
4569 $schemaIDs = Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID
4570
4571 $schemaIDs | Where-Object {$_} | ForEach-Object {
4572 # convert the GUID
4573 $GUIDs[(New-Object Guid (,$_.schemaIDGUID)).Guid] = $_.name
4574 }
4575 Remove-Variable schemaIDs
4576
4577 $schemaIDs = Get-ADObject -SearchBase "CN=Extended-Rights,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID
4578
4579 $schemaIDs | Where-Object {$_} | ForEach-Object {
4580 # convert the GUID
4581 $GUIDs[(New-Object Guid (,$_.rightsGUID)).Guid] = $_.name
4582 }
4583 Remove-Variable schemaIDs
4584
4585 # Get a list of all OUs. Add in the root containers for good measure (users, computers, etc.).
4586 $OUs = @(Get-ADDomain | Select-Object -ExpandProperty DistinguishedName)
4587 $OUs += Get-ADOrganizationalUnit -Filter * | Select-Object -ExpandProperty DistinguishedName
4588 $OUs += Get-ADObject -SearchBase (Get-ADDomain).DistinguishedName -SearchScope OneLevel -LDAPFilter '(objectClass=container)' | Select-Object -ExpandProperty DistinguishedName
4589 ForEach ($OU in $OUs)
4590 {
4591 $OUPermissions += Get-Acl -Path "$OU" |
4592 Select-Object -ExpandProperty Access |
4593 Select-Object @{name='organizationalUnit';expression={$OU}}, `
4594 @{name='objectTypeName';expression={$GUIDs[$_.objectType.ToString()]}}, `
4595 @{name='inheritedObjectTypeName';expression={$GUIDs[$_.inheritedObjectType.ToString()]}}, `
4596 *
4597 }
4598 Remove-Variable OUs
4599 Remove-Variable GUIDs
4600 }
4601 Catch
4602 {
4603 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4604 }
4605 }
4606
4607 If ($Protocol -eq 'LDAP')
4608 {
4609 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
4610 $ObjSearcher.PageSize = $PageSize
4611 $ObjSearcher.Filter = "(objectCategory=organizationalunit)"
4612 $ObjSearcher.SearchScope = "Subtree"
4613
4614 Try
4615 {
4616 $ADOUs = $ObjSearcher.FindAll()
4617 }
4618 Catch
4619 {
4620 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4621 }
4622 $ObjSearcher.dispose()
4623
4624 $OUPermissions = @()
4625 If ($ADOUs)
4626 {
4627 $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
4628
4629 If ($UseAltCreds)
4630 {
4631 $DomainFQDN = Get-DNtoFQDN($objDomain.distinguishedName)
4632 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$($DomainFQDN),$($creds.UserName),$($creds.GetNetworkCredential().password))
4633 Try
4634 {
4635 $ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
4636 }
4637 Catch
4638 {
4639 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4640 }
4641
4642 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$($ADDomain.Forest),$($creds.UserName),$($creds.GetNetworkCredential().password))
4643 Try
4644 {
4645 $ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
4646 $SchemaPath = $ADForest.Schema.Name
4647 }
4648 Catch
4649 {
4650 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4651 }
4652 }
4653 Else
4654 {
4655 $ADForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
4656 $SchemaPath = $ADForest.Schema.Name
4657 Remove-Variable SchemaPath
4658 }
4659
4660 If ($SchemaPath)
4661 {
4662 If ($UseAltCreds)
4663 {
4664 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$($SchemaPath)", $creds.UserName,$creds.GetNetworkCredential().Password
4665 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
4666 }
4667 Else
4668 {
4669 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher ([ADSI] "LDAP://$($SchemaPath)")
4670 }
4671 $objSearcherPath.PageSize = $PageSize
4672 $objSearcherPath.filter = "(schemaIDGUID=*)"
4673
4674 Try
4675 {
4676 $SchemaSearcher = $objSearcherPath.FindAll()
4677 }
4678 Catch
4679 {
4680 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4681 }
4682
4683 If ($SchemaSearcher)
4684 {
4685 $SchemaSearcher | Where-Object {$_} | ForEach-Object {
4686 # convert the GUID
4687 $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
4688 }
4689 $SchemaSearcher.dispose()
4690 }
4691 $objSearcherPath.dispose()
4692
4693 If ($UseAltCreds)
4694 {
4695 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$($SchemaPath.replace("Schema","Extended-Rights"))", $creds.UserName,$creds.GetNetworkCredential().Password
4696 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
4697 }
4698 Else
4699 {
4700 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher ([ADSI] "LDAP://$($SchemaPath.replace("Schema","Extended-Rights"))")
4701 }
4702 $objSearcherPath.PageSize = $PageSize
4703 $objSearcherPath.filter = "(objectClass=controlAccessRight)"
4704
4705 Try
4706 {
4707 $RightsSearcher = $objSearcherPath.FindAll()
4708 }
4709 Catch
4710 {
4711 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4712 }
4713
4714 If ($RightsSearcher)
4715 {
4716 $RightsSearcher | Where-Object {$_} | ForEach-Object {
4717 # convert the GUID
4718 $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
4719 }
4720 $RightsSearcher.dispose()
4721 }
4722 $objSearcherPath.dispose()
4723 }
4724 If ($UseAltCreds)
4725 {
4726 ForEach ($OU in $ADOUs)
4727 {
4728 $OUPermissions += (New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$($OU.Properties.distinguishedname)", $creds.UserName,$creds.GetNetworkCredential().Password).PsBase.ObjectSecurity.access | Select-Object @{name='organizationalUnit';expression={$OU.properties.distinguishedname}}, `
4729 @{name='objectTypeName';expression={$GUIDs[$_.objectType.ToString()]}}, `
4730 @{name='inheritedObjectTypeName';expression={$GUIDs[$_.inheritedObjectType.ToString()]}}, `
4731 *
4732 }
4733 }
4734 Else
4735 {
4736 ForEach ($OU in $ADOUs)
4737 {
4738 $OUPermissions += (($OU.GetDirectoryEntry()).Get_ObjectSecurity()).Access | Select-Object @{name='organizationalUnit';expression={$OU.properties.distinguishedname}}, `
4739 @{name='objectTypeName';expression={$GUIDs[$_.objectType.ToString()]}}, `
4740 @{name='inheritedObjectTypeName';expression={$GUIDs[$_.inheritedObjectType.ToString()]}}, `
4741 *
4742 }
4743 }
4744 Remove-Variable GUIDs
4745 Remove-Variable ADOUs
4746 }
4747 }
4748
4749 If ($OUPermissions)
4750 {
4751 Switch ($OutputType)
4752 {
4753 'STDOUT' { $OUPermissions }
4754 'CSV'
4755 {
4756 Write-Verbose "[+] Domain OrganizationalUnits Permissions"
4757 $ADFileName = -join($ReportPath,'\','OUPermissions','.csv')
4758 Try
4759 {
4760 $OUPermissions | Export-Csv -Path $ADFileName -NoTypeInformation
4761 }
4762 Catch
4763 {
4764 Write-Output "Failed to Export CSV File"
4765 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4766 }
4767 Remove-Variable OUPermissions
4768 Remove-Variable ADFileName
4769 }
4770 }
4771 }
4772}
4773
4774Function Get-ADRGPO
4775{
4776<#
4777.SYNOPSIS
4778 Returns all Group Policy Objects (GPO) in the current (or specified) domain.
4779
4780.DESCRIPTION
4781 Returns all Group Policy Objects (GPO) in the current (or specified) domain.
4782
4783.PARAMETER Protocol
4784 [string]
4785 Which protocol to use; ADWS (default) or LDAP.
4786
4787.PARAMETER UseAltCreds
4788 [bool]
4789 Whether to use provided credentials or not.
4790
4791.PARAMETER ReportPath
4792 [string]
4793 Path for ADRecon output folder.
4794
4795.PARAMETER objDomain
4796 [DirectoryServices.DirectoryEntry]
4797 Domain Directory Entry object.
4798
4799.PARAMETER creds
4800 [Management.Automation.PSCredential]
4801 Credentials.
4802
4803.PARAMETER PageSize
4804 [int]
4805 The PageSize to set for the LDAP searcher object. Default 200.
4806
4807.PARAMETER OutputType
4808 [array]
4809 Output Type.
4810
4811.OUTPUTS
4812 CSV file is created in the folder specified with the information.
4813#>
4814 param(
4815 [Parameter(Mandatory = $true)]
4816 [string] $Protocol,
4817
4818 [Parameter(Mandatory = $true)]
4819 [bool] $UseAltCreds,
4820
4821 [Parameter(Mandatory = $true)]
4822 [string] $ReportPath,
4823
4824 [Parameter(Mandatory = $false)]
4825 [DirectoryServices.DirectoryEntry] $objDomain,
4826
4827 [Parameter(Mandatory = $true)]
4828 [int] $PageSize,
4829
4830 [Parameter(Mandatory = $true)]
4831 [array] $OutputType
4832 )
4833
4834 Write-Output "[-] Domain GPOs"
4835 If ($Protocol -eq 'ADWS')
4836 {
4837 Try
4838 {
4839 $ADDomainGPOs = Get-ADObject -LDAPFilter '(objectCategory=groupPolicyContainer)' -Properties DisplayName,whenCreated,whenChanged,Name,gPCFileSysPath
4840 }
4841 Catch
4842 {
4843 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4844 }
4845
4846 If ($ADDomainGPOs)
4847 {
4848 Write-Output "[*] Total GPOs: $($ADDomainGPOs | Measure-Object | Select-Object -ExpandProperty Count)"
4849 $ADDomainGPOObj = @()
4850 $ADDomainGPOs | ForEach-Object {
4851 # Create the object for each instance.
4852 $Obj = New-Object PSObject
4853 $Obj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $_.DisplayName
4854 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value $_.whenCreated
4855 $Obj | Add-Member -MemberType NoteProperty -Name Changed -Value $_.whenChanged
4856 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value $([ADRecon.ADWSClass]::CleanString($_.Name))
4857 $Obj | Add-Member -MemberType NoteProperty -Name FilePath -Value $_.gPCFileSysPath
4858 $ADDomainGPOObj += $Obj
4859 }
4860 Remove-Variable ADDomainGPOs
4861 }
4862 }
4863
4864 If ($Protocol -eq 'LDAP')
4865 {
4866 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
4867 $ObjSearcher.PageSize = $PageSize
4868 $ObjSearcher.Filter = "(objectCategory=groupPolicyContainer)"
4869 $ObjSearcher.SearchScope = "Subtree"
4870
4871 Try
4872 {
4873 $ADDomainGPOs = $ObjSearcher.FindAll()
4874 }
4875 Catch
4876 {
4877 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4878 }
4879 $ObjSearcher.dispose()
4880
4881 If ($ADDomainGPOs)
4882 {
4883 Write-Output "[*] Total GPOs: $($ADDomainGPOs | Measure-Object | Select-Object -ExpandProperty Count)"
4884 $ADDomainGPOObj = @()
4885 $ADDomainGPOs | ForEach-Object {
4886 # Create the object for each instance.
4887 $Obj = New-Object PSObject
4888 $Obj | Add-Member -MemberType NoteProperty -Name DisplayName -Value ([string] $($_.Properties.displayname))
4889 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value ([DateTime] $($_.Properties.whencreated))
4890 $Obj | Add-Member -MemberType NoteProperty -Name Changed -Value ([DateTime] $($_.Properties.whenchanged))
4891 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value $([ADRecon.LDAPClass]::CleanString($_.Properties.name))
4892 $Obj | Add-Member -MemberType NoteProperty -Name FilePath -Value ([string] $($_.Properties.gpcfilesyspath))
4893 $ADDomainGPOObj += $Obj
4894 }
4895 Remove-Variable ADDomainGPOs
4896 }
4897 }
4898
4899 If ($ADDomainGPOObj)
4900 {
4901 Switch ($OutputType)
4902 {
4903 'STDOUT' { $ADDomainGPOObj }
4904 'CSV'
4905 {
4906 Write-Verbose "[+] Domain GPOs"
4907 $ADFileName = -join($ReportPath,'\','GPOs','.csv')
4908 Try
4909 {
4910 $ADDomainGPOObj | Export-Csv -Path $ADFileName -NoTypeInformation
4911 }
4912 Catch
4913 {
4914 Write-Output "Failed to Export CSV File"
4915 Write-Output "[EXCEPTION] $($_.Exception.Message)"
4916 }
4917 Remove-Variable ADDomainGPOObj
4918 Remove-Variable ADFileName
4919 }
4920 }
4921 }
4922}
4923
4924# Modified Convert-DNSRecord function from https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
4925Function Convert-DNSRecord
4926{
4927<#
4928.SYNOPSIS
4929
4930Helpers that decodes a binary DNS record blob.
4931
4932Author: Michael B. Smith, Will Schroeder (@harmj0y)
4933License: BSD 3-Clause
4934Required Dependencies: None
4935
4936.DESCRIPTION
4937
4938Decodes a binary blob representing an Active Directory DNS entry.
4939Used by Get-DomainDNSRecord.
4940
4941Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
4942
4943.PARAMETER DNSRecord
4944
4945A byte array representing the DNS record.
4946
4947.OUTPUTS
4948
4949System.Management.Automation.PSCustomObject
4950
4951Outputs custom PSObjects with detailed information about the DNS record entry.
4952
4953.LINK
4954
4955https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
4956#>
4957
4958 [OutputType('System.Management.Automation.PSCustomObject')]
4959 [CmdletBinding()]
4960 Param(
4961 [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
4962 [Byte[]]
4963 $DNSRecord
4964 )
4965
4966 BEGIN {
4967 Function Get-Name
4968 {
4969 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
4970 [CmdletBinding()]
4971 Param(
4972 [Byte[]]
4973 $Raw
4974 )
4975
4976 [Int]$Length = $Raw[0]
4977 [Int]$Segments = $Raw[1]
4978 [Int]$Index = 2
4979 [String]$Name = ''
4980
4981 while ($Segments-- -gt 0)
4982 {
4983 [Int]$SegmentLength = $Raw[$Index++]
4984 while ($SegmentLength-- -gt 0)
4985 {
4986 $Name += [Char]$Raw[$Index++]
4987 }
4988 $Name += "."
4989 }
4990 $Name
4991 }
4992 }
4993
4994 PROCESS
4995 {
4996 # $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
4997 $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
4998 $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
4999
5000 $TTLRaw = $DNSRecord[12..15]
5001
5002 # reverse for big endian
5003 $Null = [array]::Reverse($TTLRaw)
5004 $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
5005
5006 $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
5007 If ($Age -ne 0)
5008 {
5009 $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
5010 }
5011 Else
5012 {
5013 $TimeStamp = '[static]'
5014 }
5015
5016 $DNSRecordObject = New-Object PSObject
5017
5018 switch ($RDataType)
5019 {
5020 1
5021 {
5022 $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
5023 $Data = $IP
5024 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
5025 }
5026
5027 2
5028 {
5029 $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
5030 $Data = $NSName
5031 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
5032 }
5033
5034 5
5035 {
5036 $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
5037 $Data = $Alias
5038 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
5039 }
5040
5041 6
5042 {
5043 $PrimaryNS = Get-Name $DNSRecord[44..$DNSRecord.length]
5044 $ResponsibleParty = Get-Name $DNSRecord[$(46+$DNSRecord[44])..$DNSRecord.length]
5045 $SerialRaw = $DNSRecord[24..27]
5046 # reverse for big endian
5047 $Null = [array]::Reverse($SerialRaw)
5048 $Serial = [BitConverter]::ToUInt32($SerialRaw, 0)
5049
5050 $RefreshRaw = $DNSRecord[28..31]
5051 $Null = [array]::Reverse($RefreshRaw)
5052 $Refresh = [BitConverter]::ToUInt32($RefreshRaw, 0)
5053
5054 $RetryRaw = $DNSRecord[32..35]
5055 $Null = [array]::Reverse($RetryRaw)
5056 $Retry = [BitConverter]::ToUInt32($RetryRaw, 0)
5057
5058 $ExpiresRaw = $DNSRecord[36..39]
5059 $Null = [array]::Reverse($ExpiresRaw)
5060 $Expires = [BitConverter]::ToUInt32($ExpiresRaw, 0)
5061
5062 $MinTTLRaw = $DNSRecord[40..43]
5063 $Null = [array]::Reverse($MinTTLRaw)
5064 $MinTTL = [BitConverter]::ToUInt32($MinTTLRaw, 0)
5065
5066 $Data = "[" + $Serial + "][" + $PrimaryNS + "][" + $ResponsibleParty + "][" + $Refresh + "][" + $Retry + "][" + $Expires + "][" + $MinTTL + "]"
5067 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
5068 }
5069
5070 12
5071 {
5072 $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
5073 $Data = $Ptr
5074 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
5075 }
5076
5077 13
5078 {
5079 [string]$CPUType = ""
5080 [string]$OSType = ""
5081 [int]$SegmentLength = $DNSRecord[24]
5082 $Index = 25
5083 while ($SegmentLength-- -gt 0)
5084 {
5085 $CPUType += [char]$DNSRecord[$Index++]
5086 }
5087 $Index = 24 + $DNSRecord[24] + 1
5088 [int]$SegmentLength = $Index++
5089 while ($SegmentLength-- -gt 0)
5090 {
5091 $OSType += [char]$DNSRecord[$Index++]
5092 }
5093 $Data = "[" + $CPUType + "][" + $OSType + "]"
5094 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
5095 }
5096
5097 15
5098 {
5099 $PriorityRaw = $DNSRecord[24..25]
5100 # reverse for big endian
5101 $Null = [array]::Reverse($PriorityRaw)
5102 $Priority = [BitConverter]::ToUInt16($PriorityRaw, 0)
5103 $MXHost = Get-Name $DNSRecord[26..$DNSRecord.length]
5104 $Data = "[" + $Priority + "][" + $MXHost + "]"
5105 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
5106 }
5107
5108 16
5109 {
5110 [string]$TXT = ''
5111 [int]$SegmentLength = $DNSRecord[24]
5112 $Index = 25
5113 while ($SegmentLength-- -gt 0)
5114 {
5115 $TXT += [char]$DNSRecord[$Index++]
5116 }
5117 $Data = $TXT
5118 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
5119 }
5120
5121 28
5122 {
5123 ### yeah, this doesn't do all the fancy formatting that can be done for IPv6
5124 $AAAA = ""
5125 for ($i = 24; $i -lt 40; $i+=2)
5126 {
5127 $BlockRaw = $DNSRecord[$i..$($i+1)]
5128 # reverse for big endian
5129 $Null = [array]::Reverse($BlockRaw)
5130 $Block = [BitConverter]::ToUInt16($BlockRaw, 0)
5131 $AAAA += ($Block).ToString('x4')
5132 If ($i -ne 38)
5133 {
5134 $AAAA += ':'
5135 }
5136 }
5137 $Data = $AAAA
5138 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
5139 }
5140
5141 33
5142 {
5143 $PriorityRaw = $DNSRecord[24..25]
5144 # reverse for big endian
5145 $Null = [array]::Reverse($PriorityRaw)
5146 $Priority = [BitConverter]::ToUInt16($PriorityRaw, 0)
5147
5148 $WeightRaw = $DNSRecord[26..27]
5149 $Null = [array]::Reverse($WeightRaw)
5150 $Weight = [BitConverter]::ToUInt16($WeightRaw, 0)
5151
5152 $PortRaw = $DNSRecord[28..29]
5153 $Null = [array]::Reverse($PortRaw)
5154 $Port = [BitConverter]::ToUInt16($PortRaw, 0)
5155
5156 $SRVHost = Get-Name $DNSRecord[30..$DNSRecord.length]
5157 $Data = "[" + $Priority + "][" + $Weight + "][" + $Port + "][" + $SRVHost + "]"
5158 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
5159 }
5160
5161 default
5162 {
5163 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
5164 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
5165 }
5166 }
5167 $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
5168 $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
5169 $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
5170 $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
5171 $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
5172 Return $DNSRecordObject
5173 }
5174}
5175
5176Function Get-ADRDNSZone
5177{
5178<#
5179.SYNOPSIS
5180 Returns all DNS Zones and Records in the current (or specified) domain.
5181
5182.DESCRIPTION
5183 Returns all DNS Zones and Records in the current (or specified) domain.
5184
5185.PARAMETER Protocol
5186 [string]
5187 Which protocol to use; ADWS (default) or LDAP.
5188
5189.PARAMETER UseAltCreds
5190 [bool]
5191 Whether to use provided credentials or not.
5192
5193.PARAMETER ReportPath
5194 [string]
5195 Path for ADRecon output folder.
5196
5197.PARAMETER objDomain
5198 [DirectoryServices.DirectoryEntry]
5199 Domain Directory Entry object.
5200
5201.PARAMETER DCIP
5202 [string]
5203 IP Address of the Domain Controller.
5204
5205.PARAMETER creds
5206 [Management.Automation.PSCredential]
5207 Credentials.
5208
5209.PARAMETER PageSize
5210 [int]
5211 The PageSize to set for the LDAP searcher object. Default 200.
5212
5213.PARAMETER OutputType
5214 [array]
5215 Output Type.
5216
5217.OUTPUTS
5218 CSV files are created in the folder specified with the information.
5219#>
5220 param(
5221 [Parameter(Mandatory = $true)]
5222 [string] $Protocol,
5223
5224 [Parameter(Mandatory = $true)]
5225 [bool] $UseAltCreds,
5226
5227 [Parameter(Mandatory = $true)]
5228 [string] $ReportPath,
5229
5230 [Parameter(Mandatory = $false)]
5231 [DirectoryServices.DirectoryEntry] $objDomain,
5232
5233 [Parameter(Mandatory = $false)]
5234 [string] $DCIP,
5235
5236 [Parameter(Mandatory = $false)]
5237 [Management.Automation.PSCredential] $creds = [Management.Automation.PSCredential]::Empty,
5238
5239 [Parameter(Mandatory = $true)]
5240 [int] $PageSize,
5241
5242 [Parameter(Mandatory = $true)]
5243 [array] $OutputType
5244 )
5245
5246 Write-Output "[-] Domain DNS Zones and Records"
5247 If ($Protocol -eq 'ADWS')
5248 {
5249 Try
5250 {
5251 $ADDNSZones = Get-ADObject -LDAPFilter '(objectClass=dnsZone)' -Properties Name,whenCreated,whenChanged,usncreated,usnchanged,distinguishedname
5252 }
5253 Catch
5254 {
5255 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5256 }
5257
5258 $DNSZoneArray = @()
5259 If ($ADDNSZones)
5260 {
5261 $DNSZoneArray += $ADDNSZones
5262 Remove-Variable ADDNSZones
5263 }
5264
5265 Try
5266 {
5267 $ADDNSZones1 = Get-ADObject -LDAPFilter '(objectClass=dnsZone)' -SearchBase "CN=MicrosoftDNS,DC=DomainDnsZones,$((Get-ADDomain).DistinguishedName)" -Properties Name,whenCreated,whenChanged,usncreated,usnchanged,distinguishedname
5268 }
5269 Catch
5270 {
5271 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5272 }
5273 If ($ADDNSZones1)
5274 {
5275 $DNSZoneArray += $ADDNSZones1
5276 Remove-Variable ADDNSZones1
5277 }
5278
5279 Try
5280 {
5281 $ADDNSZones2 = Get-ADObject -LDAPFilter '(objectClass=dnsZone)' -SearchBase "CN=MicrosoftDNS,DC=ForestDnsZones,$((Get-ADDomain).DistinguishedName)" -Properties Name,whenCreated,whenChanged,usncreated,usnchanged,distinguishedname
5282 }
5283 Catch
5284 {
5285 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5286 }
5287 If ($ADDNSZones2)
5288 {
5289 $DNSZoneArray += $ADDNSZones2
5290 Remove-Variable ADDNSZones2
5291 }
5292
5293 Write-Output "[*] Total DNS Zones: $($DNSZoneArray | Measure-Object | Select-Object -ExpandProperty Count)"
5294
5295 If ($DNSZoneArray)
5296 {
5297 $ADDNSZonesObj = @()
5298 $ADDNSNodesObj = @()
5299 $DNSZoneArray | ForEach-Object {
5300 # Create the object for each instance.
5301 $Obj = New-Object PSObject
5302 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value $([ADRecon.ADWSClass]::CleanString($_.Name))
5303 $Obj | Add-Member -MemberType NoteProperty -Name USNCreated -Value $_.usncreated
5304 $Obj | Add-Member -MemberType NoteProperty -Name USNChanged -Value $_.usnchanged
5305 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value $_.whenCreated
5306 $Obj | Add-Member -MemberType NoteProperty -Name Changed -Value $_.whenChanged
5307 Try
5308 {
5309 $DNSNodes = Get-ADObject -SearchBase $($_.DistinguishedName) -LDAPFilter '(objectClass=dnsNode)' -Properties DistinguishedName,dnsrecord,dNSTombstoned,Name,ProtectedFromAccidentalDeletion,showInAdvancedViewOnly,whenChanged,whenCreated
5310 }
5311 Catch
5312 {
5313 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5314 }
5315 If ($DNSNodes)
5316 {
5317 $Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $($DNSNodes | Measure-Object | Select-Object -ExpandProperty Count)
5318 $DNSNodes | ForEach-Object {
5319 $ObjNode = New-Object PSObject
5320 $ObjNode | Add-Member -MemberType NoteProperty -Name ZoneName -Value $Obj.Name
5321 $ObjNode | Add-Member -MemberType NoteProperty -Name Name -Value $_.Name
5322 Try
5323 {
5324 $DNSRecord = Convert-DNSRecord $_.dnsrecord[0]
5325 }
5326 Catch
5327 {
5328 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5329 }
5330 $ObjNode | Add-Member -MemberType NoteProperty -Name RecordType -Value $DNSRecord.RecordType
5331 $ObjNode | Add-Member -MemberType NoteProperty -Name Data -Value $DNSRecord.Data
5332 $ObjNode | Add-Member -MemberType NoteProperty -Name TTL -Value $DNSRecord.TTL
5333 $ObjNode | Add-Member -MemberType NoteProperty -Name Age -Value $DNSRecord.Age
5334 $ObjNode | Add-Member -MemberType NoteProperty -Name TimeStamp -Value $DNSRecord.TimeStamp
5335 $ObjNode | Add-Member -MemberType NoteProperty -Name UpdatedAtSerial -Value $DNSRecord.UpdatedAtSerial
5336 $ObjNode | Add-Member -MemberType NoteProperty -Name Created -Value $_.whenCreated
5337 $ObjNode | Add-Member -MemberType NoteProperty -Name Changed -Value $_.whenChanged
5338 # TO DO LDAP part
5339 #$ObjNode | Add-Member -MemberType NoteProperty -Name dNSTombstoned -Value $_.dNSTombstoned
5340 #$ObjNode | Add-Member -MemberType NoteProperty -Name ProtectedFromAccidentalDeletion -Value $_.ProtectedFromAccidentalDeletion
5341 $ObjNode | Add-Member -MemberType NoteProperty -Name showInAdvancedViewOnly -Value $_.showInAdvancedViewOnly
5342 $ObjNode | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value $_.DistinguishedName
5343 $ADDNSNodesObj += $ObjNode
5344 If ($DNSRecord)
5345 {
5346 Remove-Variable DNSRecord
5347 }
5348 }
5349 }
5350 Else
5351 {
5352 $Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $null
5353 }
5354 $Obj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value $_.DistinguishedName
5355 $ADDNSZonesObj += $Obj
5356 }
5357 Remove-Variable DNSZoneArray
5358 }
5359 }
5360
5361 If ($Protocol -eq 'LDAP')
5362 {
5363 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
5364 $ObjSearcher.PageSize = $PageSize
5365 $ObjSearcher.PropertiesToLoad.AddRange(("name","whencreated","whenchanged","usncreated","usnchanged","distinguishedname"))
5366 $ObjSearcher.Filter = "(objectClass=dnsZone)"
5367 $ObjSearcher.SearchScope = "Subtree"
5368
5369 Try
5370 {
5371 $ADDNSZones = $ObjSearcher.FindAll()
5372 }
5373 Catch
5374 {
5375 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5376 }
5377 $ObjSearcher.dispose()
5378
5379 $DNSZoneArray = @()
5380 If ($ADDNSZones)
5381 {
5382 $DNSZoneArray += $ADDNSZones
5383 Remove-Variable ADDNSZones
5384 }
5385
5386 $SearchPath = "CN=MicrosoftDNS,DC=DomainDnsZones"
5387 If ($UseAltCreds)
5388 {
5389 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$($SearchPath),$($objDomain.distinguishedName)", $creds.UserName,$creds.GetNetworkCredential().Password
5390 }
5391 Else
5392 {
5393 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($SearchPath),$($objDomain.distinguishedName)"
5394 }
5395 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
5396 $objSearcherPath.Filter = "(objectClass=dnsZone)"
5397 $objSearcherPath.PageSize = $PageSize
5398 $objSearcherPath.PropertiesToLoad.AddRange(("name","whencreated","whenchanged","usncreated","usnchanged","distinguishedname"))
5399 $objSearcherPath.SearchScope = "Subtree"
5400
5401 Try
5402 {
5403 $ADDNSZones1 = $objSearcherPath.FindAll()
5404 }
5405 Catch
5406 {
5407 Write-Warning "Try running with a Privileged Account"
5408 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5409 }
5410 $objSearcherPath.dispose()
5411
5412 If ($ADDNSZones1)
5413 {
5414 $DNSZoneArray += $ADDNSZones1
5415 Remove-Variable ADDNSZones1
5416 }
5417
5418 $SearchPath = "CN=MicrosoftDNS,DC=ForestDnsZones"
5419 If ($UseAltCreds)
5420 {
5421 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$($SearchPath),$($objDomain.distinguishedName)", $creds.UserName,$creds.GetNetworkCredential().Password
5422 }
5423 Else
5424 {
5425 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($SearchPath),$($objDomain.distinguishedName)"
5426 }
5427 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
5428 $objSearcherPath.Filter = "(objectClass=dnsZone)"
5429 $objSearcherPath.PageSize = $PageSize
5430 $objSearcherPath.PropertiesToLoad.AddRange(("name","whencreated","whenchanged","usncreated","usnchanged","distinguishedname"))
5431 $objSearcherPath.SearchScope = "Subtree"
5432
5433 Try
5434 {
5435 $ADDNSZones2 = $objSearcherPath.FindAll()
5436 }
5437 Catch
5438 {
5439 Write-Warning "Try running with a Privileged Account"
5440 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5441 }
5442 $objSearcherPath.dispose()
5443
5444 If ($ADDNSZones2)
5445 {
5446 $DNSZoneArray += $ADDNSZones2
5447 Remove-Variable ADDNSZones2
5448 }
5449
5450 Write-Output "[*] Total DNS Zones: $($DNSZoneArray | Measure-Object | Select-Object -ExpandProperty Count)"
5451
5452 If ($DNSZoneArray)
5453 {
5454 $ADDNSZonesObj = @()
5455 $ADDNSNodesObj = @()
5456 $DNSZoneArray | ForEach-Object {
5457 If ($UseAltCreds)
5458 {
5459 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/$($_.Properties.distinguishedname)", $creds.UserName,$creds.GetNetworkCredential().Password
5460 }
5461 Else
5462 {
5463 $objSearchPath = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($_.Properties.distinguishedname)"
5464 }
5465 $objSearcherPath = New-Object System.DirectoryServices.DirectorySearcher $objSearchPath
5466 $objSearcherPath.Filter = "(objectClass=dnsNode)"
5467 $objSearcherPath.PageSize = $PageSize
5468 $objSearcherPath.PropertiesToLoad.AddRange(("distinguishedname","dnsrecord","name","dc","showinadvancedviewonly","whenchanged","whencreated"))
5469 Try
5470 {
5471 $DNSNodes = $objSearcherPath.FindAll()
5472 }
5473 Catch
5474 {
5475 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5476 }
5477 $objSearcherPath.dispose()
5478 Remove-Variable objSearchPath
5479
5480 # Create the object for each instance.
5481 $Obj = New-Object PSObject
5482 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value $([ADRecon.LDAPClass]::CleanString($_.Properties.name))
5483 $Obj | Add-Member -MemberType NoteProperty -Name USNCreated -Value ([string] $($_.Properties.usncreated))
5484 $Obj | Add-Member -MemberType NoteProperty -Name USNChanged -Value ([string] $($_.Properties.usnchanged))
5485 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value ([DateTime] $($_.Properties.whencreated))
5486 $Obj | Add-Member -MemberType NoteProperty -Name Changed -Value ([DateTime] $($_.Properties.whenchanged))
5487 If ($DNSNodes)
5488 {
5489 $Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $($DNSNodes | Measure-Object | Select-Object -ExpandProperty Count)
5490 $DNSNodes | ForEach-Object {
5491 $ObjNode = New-Object PSObject
5492 $ObjNode | Add-Member -MemberType NoteProperty -Name ZoneName -Value $Obj.Name
5493 $name = ([string] $($_.Properties.name))
5494 If (-Not $name)
5495 {
5496 $name = ([string] $($_.Properties.dc))
5497 }
5498 $ObjNode | Add-Member -MemberType NoteProperty -Name Name -Value $name
5499 Try
5500 {
5501 $DNSRecord = Convert-DNSRecord $_.Properties.dnsrecord[0]
5502 }
5503 Catch
5504 {
5505 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5506 }
5507 $ObjNode | Add-Member -MemberType NoteProperty -Name RecordType -Value $DNSRecord.RecordType
5508 $ObjNode | Add-Member -MemberType NoteProperty -Name Data -Value $DNSRecord.Data
5509 $ObjNode | Add-Member -MemberType NoteProperty -Name TTL -Value $DNSRecord.TTL
5510 $ObjNode | Add-Member -MemberType NoteProperty -Name Age -Value $DNSRecord.Age
5511 $ObjNode | Add-Member -MemberType NoteProperty -Name TimeStamp -Value $DNSRecord.TimeStamp
5512 $ObjNode | Add-Member -MemberType NoteProperty -Name UpdatedAtSerial -Value $DNSRecord.UpdatedAtSerial
5513 $ObjNode | Add-Member -MemberType NoteProperty -Name Created -Value ([DateTime] $($_.Properties.whencreated))
5514 $ObjNode | Add-Member -MemberType NoteProperty -Name Changed -Value ([DateTime] $($_.Properties.whenchanged))
5515 # TO DO
5516 #$ObjNode | Add-Member -MemberType NoteProperty -Name dNSTombstoned -Value $null
5517 #$ObjNode | Add-Member -MemberType NoteProperty -Name ProtectedFromAccidentalDeletion -Value $null
5518 $ObjNode | Add-Member -MemberType NoteProperty -Name showInAdvancedViewOnly -Value ([string] $($_.Properties.showinadvancedviewonly))
5519 $ObjNode | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value ([string] $($_.Properties.distinguishedname))
5520 $ADDNSNodesObj += $ObjNode
5521 If ($DNSRecord)
5522 {
5523 Remove-Variable DNSRecord
5524 }
5525 }
5526 }
5527 Else
5528 {
5529 $Obj | Add-Member -MemberType NoteProperty -Name RecordCount -Value $null
5530 }
5531 $Obj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value ([string] $($_.Properties.distinguishedname))
5532 $ADDNSZonesObj += $Obj
5533 }
5534 Remove-Variable DNSZoneArray
5535 }
5536 }
5537
5538 If ($ADDNSZonesObj)
5539 {
5540 Switch ($OutputType)
5541 {
5542 'STDOUT' { $ADDNSZonesObj }
5543 'CSV'
5544 {
5545 Write-Verbose "[+] Domain DNS Zones"
5546 $ADFileName = -join($ReportPath,'\','DNSZones','.csv')
5547 Try
5548 {
5549 $ADDNSZonesObj | Export-Csv -Path $ADFileName -NoTypeInformation
5550 }
5551 Catch
5552 {
5553 Write-Output "Failed to Export CSV File"
5554 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5555 }
5556 Remove-Variable ADDNSZonesObj
5557 Remove-Variable ADFileName
5558 }
5559 }
5560 }
5561
5562 If ($ADDNSNodesObj)
5563 {
5564 Switch ($OutputType)
5565 {
5566 'STDOUT' { $ADDNSNodesObj }
5567 'CSV'
5568 {
5569 Write-Verbose "[+] Domain DNS Records"
5570 $ADFileName = -join($ReportPath,'\','DNSNodes','.csv')
5571 Try
5572 {
5573 $ADDNSNodesObj | Export-Csv -Path $ADFileName -NoTypeInformation
5574 }
5575 Catch
5576 {
5577 Write-Output "Failed to Export CSV File"
5578 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5579 }
5580 Remove-Variable ADDNSNodesObj
5581 Remove-Variable ADFileName
5582 }
5583 }
5584 }
5585}
5586
5587Function Get-ADRPrinter
5588{
5589<#
5590.SYNOPSIS
5591 Returns all printers in the current (or specified) domain.
5592
5593.DESCRIPTION
5594 Returns all printers in the current (or specified) domain.
5595
5596.PARAMETER Protocol
5597 [string]
5598 Which protocol to use; ADWS (default) or LDAP.
5599
5600.PARAMETER UseAltCreds
5601 [bool]
5602 Whether to use provided credentials or not.
5603
5604.PARAMETER ReportPath
5605 [string]
5606 Path for ADRecon output folder.
5607
5608.PARAMETER objDomain
5609 [DirectoryServices.DirectoryEntry]
5610 Domain Directory Entry object.
5611
5612.PARAMETER PageSize
5613 [int]
5614 The PageSize to set for the LDAP searcher object. Default 200.
5615
5616.PARAMETER OutputType
5617 [array]
5618 Output Type.
5619
5620.OUTPUTS
5621 CSV file is created in the folder specified with the information.
5622#>
5623
5624 param(
5625 [Parameter(Mandatory = $true)]
5626 [string] $Protocol,
5627
5628 [Parameter(Mandatory = $true)]
5629 [bool] $UseAltCreds,
5630
5631 [Parameter(Mandatory = $true)]
5632 [string] $ReportPath,
5633
5634 [Parameter(Mandatory = $false)]
5635 [DirectoryServices.DirectoryEntry] $objDomain,
5636
5637 [Parameter(Mandatory = $true)]
5638 [int] $PageSize,
5639
5640 [Parameter(Mandatory = $true)]
5641 [array] $OutputType
5642 )
5643
5644 Write-Output "[-] Domain Printers"
5645 If ($Protocol -eq 'ADWS')
5646 {
5647 Try
5648 {
5649 $ADPrinters = Get-ADObject -LDAPFilter '(objectCategory=printQueue)' -Properties serverName,printShareName,driverName,driverVersion,portName,url,whenCreated,whenChanged,Name
5650 }
5651 Catch
5652 {
5653 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5654 }
5655
5656 If ($ADPrinters)
5657 {
5658 $cnt = $($ADPrinters | Measure-Object | Select-Object -ExpandProperty Count)
5659 If ($cnt -ge 1)
5660 {
5661 Write-Output "[*] Total Printers: $cnt"
5662 $ADPrintersObj = @()
5663 $ADPrinters | ForEach-Object {
5664 # Create the object for each instance.
5665 $Obj = New-Object PSObject
5666 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value $_.Name
5667 $Obj | Add-Member -MemberType NoteProperty -Name ServerName -Value $_.serverName
5668 $Obj | Add-Member -MemberType NoteProperty -Name ShareName -Value ([string]($_.printShareName))
5669 $Obj | Add-Member -MemberType NoteProperty -Name DriverName -Value $_.driverName
5670 $Obj | Add-Member -MemberType NoteProperty -Name DriverVersion -Value $_.driverVersion
5671 $Obj | Add-Member -MemberType NoteProperty -Name PortName -Value ([string]($_.portName))
5672 $Obj | Add-Member -MemberType NoteProperty -Name URL -Value ([string]($_.url))
5673 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value $_.whenCreated
5674 $Obj | Add-Member -MemberType NoteProperty -Name Changed -Value $_.whenChanged
5675 $ADPrintersObj += $Obj
5676 }
5677 }
5678 Remove-Variable ADPrinters
5679 }
5680 }
5681
5682 If ($Protocol -eq 'LDAP')
5683 {
5684 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
5685 $ObjSearcher.PageSize = $PageSize
5686 $ObjSearcher.Filter = "(objectCategory=printQueue)"
5687 $ObjSearcher.SearchScope = "Subtree"
5688
5689 Try
5690 {
5691 $ADPrinters = $ObjSearcher.FindAll()
5692 }
5693 Catch
5694 {
5695 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5696 }
5697 $ObjSearcher.dispose()
5698
5699 If ($ADPrinters)
5700 {
5701 $cnt = $($ADPrinters | Measure-Object | Select-Object -ExpandProperty Count)
5702 If ($cnt -ge 1)
5703 {
5704 Write-Output "[*] Total Printers: $cnt"
5705 $ADPrintersObj = @()
5706 $ADPrinters | ForEach-Object {
5707 # Create the object for each instance.
5708 $Obj = New-Object PSObject
5709 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value ([string] $($_.Properties.name))
5710 $Obj | Add-Member -MemberType NoteProperty -Name ServerName -Value ([string] $($_.Properties.servername))
5711 $Obj | Add-Member -MemberType NoteProperty -Name ShareName -Value ([string] $($_.Properties.printsharename))
5712 $Obj | Add-Member -MemberType NoteProperty -Name DriverName -Value ([string] $($_.Properties.drivername))
5713 $Obj | Add-Member -MemberType NoteProperty -Name DriverVersion -Value ([string] $($_.Properties.driverversion))
5714 $Obj | Add-Member -MemberType NoteProperty -Name PortName -Value ([string] $($_.Properties.portname))
5715 $Obj | Add-Member -MemberType NoteProperty -Name URL -Value ([string] $($_.Properties.url))
5716 $Obj | Add-Member -MemberType NoteProperty -Name Created -Value ([DateTime] $($_.Properties.whencreated))
5717 $Obj | Add-Member -MemberType NoteProperty -Name Changed -Value ([DateTime] $($_.Properties.whenchanged))
5718 $ADPrintersObj += $Obj
5719 }
5720 }
5721 Remove-Variable ADPrinters
5722 }
5723 }
5724
5725
5726 If ($ADPrintersObj)
5727 {
5728 Switch ($OutputType)
5729 {
5730 'STDOUT' { $ADPrintersObj }
5731 'CSV'
5732 {
5733 Write-Verbose "[+] Domain Printers"
5734 $ADFileName = -join($ReportPath,'\','Printers','.csv')
5735 Try
5736 {
5737 $ADPrintersObj | Export-Csv -Path $ADFileName -NoTypeInformation
5738 }
5739 Catch
5740 {
5741 Write-Output "Failed to Export CSV File"
5742 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5743 }
5744 Remove-Variable ADPrintersObj
5745 Remove-Variable ADFileName
5746 }
5747 }
5748 }
5749}
5750
5751Function Get-ADRComputer
5752{
5753<#
5754.SYNOPSIS
5755 Returns all computers in the current (or specified) domain.
5756
5757.DESCRIPTION
5758 Returns all computers in the current (or specified) domain.
5759
5760.PARAMETER Protocol
5761 [string]
5762 Which protocol to use; ADWS (default) or LDAP.
5763
5764.PARAMETER UseAltCreds
5765 [bool]
5766 Whether to use provided credentials or not.
5767
5768.PARAMETER ReportPath
5769 [string]
5770 Path for ADRecon output folder.
5771
5772.PARAMETER date
5773 [DateTime]
5774 Date when ADRecon was executed.
5775
5776.PARAMETER objDomain
5777 [DirectoryServices.DirectoryEntry]
5778 Domain Directory Entry object.
5779
5780.PARAMETER PageSize
5781 [int]
5782 The PageSize to set for the LDAP searcher object. Default 200.
5783
5784.PARAMETER Threads
5785 [int]
5786 The number of threads to use during processing of objects. Default 10.
5787
5788.PARAMETER FlushCount
5789 [int]
5790 The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).
5791
5792.PARAMETER OutputType
5793 [array]
5794 Output Type.
5795
5796.OUTPUTS
5797 CSV file is created in the folder specified with the information.
5798#>
5799 param(
5800 [Parameter(Mandatory = $true)]
5801 [string] $Protocol,
5802
5803 [Parameter(Mandatory = $true)]
5804 [bool] $UseAltCreds,
5805
5806 [Parameter(Mandatory = $true)]
5807 [string] $ReportPath,
5808
5809 [Parameter(Mandatory = $true)]
5810 [DateTime] $date,
5811
5812 [Parameter(Mandatory = $false)]
5813 [DirectoryServices.DirectoryEntry] $objDomain,
5814
5815 [Parameter(Mandatory = $true)]
5816 [int] $PageSize,
5817
5818 [Parameter(Mandatory = $false)]
5819 [int] $Threads = 10,
5820
5821 [Parameter(Mandatory = $false)]
5822 [int] $FlushCount = -1,
5823
5824 [Parameter(Mandatory = $true)]
5825 [array] $OutputType
5826 )
5827
5828 Write-Output "[-] Domain Computers - May take some time"
5829 If ($Protocol -eq 'ADWS')
5830 {
5831 Try
5832 {
5833 $ADComputers = Get-ADComputer -Filter * -ResultPageSize $PageSize -Properties Name,DNSHostName,Description,Enabled,IPv4Address,OperatingSystem,LastLogonDate,PasswordLastSet,primaryGroupID,TrustedForDelegation,TrustedToAuthForDelegation,SamAccountName,whenChanged,whenCreated,DistinguishedName
5834 }
5835 Catch
5836 {
5837 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5838 }
5839
5840 If ($ADComputers)
5841 {
5842 $ADFileName = -join($ReportPath,'\','Computers','.csv')
5843 $ComputerCount = [ADRecon.ADWSClass]::ComputerParser($ADComputers, $date, $ADFileName, $Threads, $FlushCount, $OutputType)
5844 # Temporary solution for exception in [ADRecon.ADWSClass]::ComputerParser
5845 # System.InvalidCastException: Unable to cast object of type 'Microsoft.ActiveDirectory.Management.ADComputer' to type 'System.Management.Automation.PSObject'.
5846 If ($ComputerCount -eq 1)
5847 {
5848 $ADComputers | ForEach-Object {
5849 $Obj = New-Object PSObject
5850 $Obj | Add-Member -MemberType NoteProperty -Name Name -Value $($_.Name)
5851 $Obj | Add-Member -MemberType NoteProperty -Name DNSHostName -Value $($_.DNSHostName)
5852 $Obj | Add-Member -MemberType NoteProperty -Name Enabled -Value $($_.Enabled)
5853 $Obj | Add-Member -MemberType NoteProperty -Name IPv4Address -Value $($_.IPv4Address)
5854 If ($null -ne $_.OperatingSystem)
5855 {
5856 $Obj | Add-Member -MemberType NoteProperty -Name OperatingSystem -Value $($_.OperatingSystem)
5857 }
5858 Else
5859 {
5860 $Obj | Add-Member -MemberType NoteProperty -Name OperatingSystem -Value "-"
5861 }
5862 If ($null -eq $_.LastLogonDate)
5863 {
5864 $Obj | Add-Member -MemberType NoteProperty -Name "Days Since Last Logon" -Value "-"
5865 }
5866 Else
5867 {
5868 $DDiff = (Get-DateDiff $_.LastLogonDate $date).Days
5869 $Obj | Add-Member -MemberType NoteProperty -Name "Days Since Last Logon" -Value $DDiff
5870 }
5871 If ($null -eq $_.PasswordLastSet)
5872 {
5873 $Obj | Add-Member -MemberType NoteProperty -Name "Days Since Last Password Change" -Value "-"
5874 }
5875 Else
5876 {
5877 $DDiff = (Get-DateDiff $_.PasswordLastSet $date).Days
5878 $Obj | Add-Member -MemberType NoteProperty -Name "Days Since Last Password Change" -Value $DDiff
5879 }
5880 $Obj | Add-Member -MemberType NoteProperty -Name "Trusted for Delegation" -Value $($_.TrustedForDelegation)
5881 $Obj | Add-Member -MemberType NoteProperty -Name "Trusted to Auth for Delegation" -Value $($_.TrustedToAuthForDelegation)
5882 $Obj | Add-Member -MemberType NoteProperty -Name "Username" -Value $($_.SamAccountName)
5883 $Obj | Add-Member -MemberType NoteProperty -Name "Primary Group ID" -Value $($_.primaryGroupID)
5884 $Obj | Add-Member -MemberType NoteProperty -Name "Description" -Value $($_.Description)
5885 If ($null -eq $_.PasswordLastSet)
5886 {
5887 $Obj | Add-Member -MemberType NoteProperty -Name "Password LastSet" -Value "-"
5888 }
5889 Else
5890 {
5891 $Obj | Add-Member -MemberType NoteProperty -Name "Password LastSet" -Value $($_.PasswordLastSet)
5892 }
5893 If ($null -eq $_.LastLogonDate)
5894 {
5895 $Obj | Add-Member -MemberType NoteProperty -Name "Last Logon Date" -Value "-"
5896 }
5897 Else
5898 {
5899 $Obj | Add-Member -MemberType NoteProperty -Name "Last Logon Date" -Value $($_.LastLogonDate)
5900 }
5901 $Obj | Add-Member -MemberType NoteProperty -Name "whenCreated" -Value $($_.whenCreated)
5902 $Obj | Add-Member -MemberType NoteProperty -Name "whenChanged" -Value $($_.whenChanged)
5903 $Obj | Add-Member -MemberType NoteProperty -Name 'Distinguished Name' -Value $($_.DistinguishedName)
5904 Switch ($OutputType)
5905 {
5906 'STDOUT' { $Obj }
5907 'CSV'
5908 {
5909 Try
5910 {
5911 $Obj | Export-Csv -Path $ADFileName -NoTypeInformation
5912 }
5913 Catch
5914 {
5915 Write-Output "[ERROR] Failed to Export CSV File"
5916 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5917 }
5918 }
5919 }
5920 }
5921 }
5922 Remove-Variable ADComputers
5923 Write-Verbose "[+] Domain Computers"
5924 }
5925 }
5926
5927 If ($Protocol -eq 'LDAP')
5928 {
5929 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
5930 $ObjSearcher.PageSize = $PageSize
5931 $ObjSearcher.Filter = "(samAccountType=805306369)"
5932 $ObjSearcher.PropertiesToLoad.AddRange(("description","name","pwdlastset","useraccountcontrol","samaccountname","dnshostname","lastlogontimestamp","primarygroupid","whenchanged","whencreated","operatingsystem","distinguishedname"))
5933 $ObjSearcher.SearchScope = "Subtree"
5934
5935 Try
5936 {
5937 $ADComputers = $ObjSearcher.FindAll()
5938 }
5939 Catch
5940 {
5941 Write-Output "[EXCEPTION] $($_.Exception.Message)"
5942 Return $null
5943 }
5944 $ObjSearcher.dispose()
5945
5946 If ($ADComputers)
5947 {
5948 $ADFileName = -join($ReportPath,'\','Computers','.csv')
5949 [ADRecon.LDAPClass]::ComputerParser($ADComputers, $date, $ADFileName, $Threads, $FlushCount, $OutputType)
5950 Remove-Variable ADComputers
5951 Write-Verbose "[+] Domain Computers"
5952 }
5953 }
5954}
5955
5956Function Get-ADRComputerSPN
5957{
5958<#
5959.SYNOPSIS
5960 Returns all computer service principal name (SPN) in the current (or specified) domain.
5961
5962.DESCRIPTION
5963 Returns all computer service principal name (SPN) in the current (or specified) domain.
5964
5965.PARAMETER Protocol
5966 [string]
5967 Which protocol to use; ADWS (default) or LDAP.
5968
5969.PARAMETER UseAltCreds
5970 [bool]
5971 Whether to use provided credentials or not.
5972
5973.PARAMETER ReportPath
5974 [string]
5975 Path for ADRecon output folder.
5976
5977.PARAMETER objDomain
5978 [DirectoryServices.DirectoryEntry]
5979 Domain Directory Entry object.
5980
5981.PARAMETER PageSize
5982 [int]
5983 The PageSize to set for the LDAP searcher object. Default 200.
5984
5985.PARAMETER Threads
5986 [int]
5987 The number of threads to use during processing of objects. Default 10.
5988
5989.PARAMETER FlushCount
5990 [int]
5991 The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).
5992
5993.PARAMETER OutputType
5994 [array]
5995 Output Type.
5996
5997.OUTPUTS
5998 CSV file is created in the folder specified with the information.
5999#>
6000 param(
6001 [Parameter(Mandatory = $true)]
6002 [string] $Protocol,
6003
6004 [Parameter(Mandatory = $true)]
6005 [bool] $UseAltCreds,
6006
6007 [Parameter(Mandatory = $true)]
6008 [string] $ReportPath,
6009
6010 [Parameter(Mandatory = $false)]
6011 [DirectoryServices.DirectoryEntry] $objDomain,
6012
6013 [Parameter(Mandatory = $true)]
6014 [int] $PageSize,
6015
6016 [Parameter(Mandatory = $false)]
6017 [int] $Threads = 10,
6018
6019 [Parameter(Mandatory = $false)]
6020 [int] $FlushCount = -1,
6021
6022 [Parameter(Mandatory = $true)]
6023 [array] $OutputType
6024 )
6025
6026 Write-Output "[-] Domain Computer SPNs"
6027 If ($Protocol -eq 'ADWS')
6028 {
6029 Try
6030 {
6031 $ADComputers = Get-ADObject -LDAPFilter "(&(objectClass=computer)(servicePrincipalName=*))" -Properties name,dnshostname,servicePrincipalName -ResultPageSize $PageSize
6032 }
6033 Catch
6034 {
6035 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6036 }
6037
6038 If ($ADComputers)
6039 {
6040 $ADFileName = -join($ReportPath,'\','ComputerSPNs','.csv')
6041 $ComputerSPNCount = [ADRecon.ADWSClass]::ComputerSPNParser($ADComputers, $ADFileName, $Threads, $FlushCount, $OutputType)
6042 # Temporary solution for exception in [ADRecon.ADWSClass]::ComputerSPNParser
6043 # System.InvalidCastException: Unable to cast object of type 'Microsoft.ActiveDirectory.Management.ADComputer' to type 'System.Management.Automation.PSObject'.
6044 If ($ComputerSPNCount -eq 1)
6045 {
6046 $CompSPNObj = @()
6047 $ADComputers | ForEach-Object {
6048 For($i=0; $i -lt $_.servicePrincipalName.count; $i++)
6049 {
6050 $Obj = New-Object PSObject
6051 [array] $SPNObjectArray = $_.servicePrincipalName[$i] -Split("/")
6052 $Obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $_.Name
6053 $Obj | Add-Member -MemberType NoteProperty -Name "Service" -Value $SPNObjectArray[0]
6054 $Obj | Add-Member -MemberType NoteProperty -Name "Host" -Value $SPNObjectArray[1]
6055 $CompSPNObj += $Obj
6056 Remove-Variable SPNObjectArray
6057 }
6058 }
6059 If ($CompSPNObj)
6060 {
6061 Switch ($OutputType)
6062 {
6063 'STDOUT' { $CompSPNObj }
6064 'CSV'
6065 {
6066 $ADFileName = -join($ReportPath,'\','ComputerSPNs','.csv')
6067 Try
6068 {
6069 $CompSPNObj | Export-Csv -Path $ADFileName -NoTypeInformation
6070 }
6071 Catch
6072 {
6073 Write-Output "Failed to Export CSV File"
6074 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6075 }
6076 }
6077 }
6078 }
6079 }
6080 Remove-Variable ADComputers
6081 Write-Verbose "[+] Domain Computer SPNs"
6082 }
6083 }
6084
6085 If ($Protocol -eq 'LDAP')
6086 {
6087 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
6088 $ObjSearcher.PageSize = $PageSize
6089 $ObjSearcher.Filter = "(&(objectClass=computer)(servicePrincipalName=*))"
6090 $ObjSearcher.PropertiesToLoad.AddRange(("name","samaccountname","serviceprincipalname","pwdlastset","description"))
6091 $ObjSearcher.SearchScope = "Subtree"
6092 Try
6093 {
6094 $ADComputers = $ObjSearcher.FindAll()
6095 }
6096 Catch
6097 {
6098 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6099 Return $null
6100 }
6101 $ObjSearcher.dispose()
6102
6103 If ($ADComputers)
6104 {
6105 $ADFileName = -join($ReportPath,'\','ComputerSPNs','.csv')
6106 [ADRecon.LDAPClass]::ComputerSPNParser($ADComputers, $ADFileName, $Threads, $FlushCount, $OutputType)
6107 Remove-Variable ADComputers
6108 Write-Verbose "[+] Domain Computer SPNs"
6109 }
6110 }
6111}
6112
6113Function Get-ADRLAPSCheck
6114{
6115<#
6116.SYNOPSIS
6117 Returns all LAPS (local administrator) stored passwords in the current (or specified) domain.
6118
6119.DESCRIPTION
6120 Returns all LAPS (local administrator) stored passwords in the current (or specified) domain. Other details such as the Password Expiration, whether the password is readable by the current user are also returned.
6121
6122.PARAMETER Protocol
6123 [string]
6124 Which protocol to use; ADWS (default) or LDAP.
6125
6126.PARAMETER UseAltCreds
6127 [bool]
6128 Whether to use provided credentials or not.
6129
6130.PARAMETER ReportPath
6131 [string]
6132 Path for ADRecon output folder.
6133
6134.PARAMETER objDomain
6135 [DirectoryServices.DirectoryEntry]
6136 Domain Directory Entry object.
6137
6138.PARAMETER PageSize
6139 [int]
6140 The PageSize to set for the LDAP searcher object. Default 200.
6141
6142.PARAMETER OutputType
6143 [array]
6144 Output Type.
6145
6146.OUTPUTS
6147 CSV file is created in the folder specified with the information.
6148#>
6149 param(
6150 [Parameter(Mandatory = $true)]
6151 [string] $Protocol,
6152
6153 [Parameter(Mandatory = $true)]
6154 [bool] $UseAltCreds,
6155
6156 [Parameter(Mandatory = $true)]
6157 [string] $ReportPath,
6158
6159 [Parameter(Mandatory = $false)]
6160 [DirectoryServices.DirectoryEntry] $objDomain,
6161
6162 [Parameter(Mandatory = $true)]
6163 [int] $PageSize,
6164
6165 [Parameter(Mandatory = $true)]
6166 [array] $OutputType
6167 )
6168
6169 Write-Output "[-] LAPS - Needs Privileged Account"
6170 # based on https://github.com/kfosaaen/Get-LAPSPasswords/blob/master/Get-LAPSPasswords.ps1
6171 If ($Protocol -eq 'ADWS')
6172 {
6173 Try
6174 {
6175 $ADComputers = Get-ADObject -LDAPFilter "(objectClass=computer)" -Properties cn,dnshostname,'ms-mcs-admpwd','ms-mcs-admpwdexpirationtime' -ResultPageSize $PageSize
6176 }
6177 Catch [System.ArgumentException]
6178 {
6179 Write-Output "[*] LAPS is not implemented."
6180 $LAPS = $false
6181 }
6182 Catch
6183 {
6184 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6185 }
6186
6187 If ($ADComputers -and $LAPS -ne $false)
6188 {
6189 $LAPSObj = @()
6190 $ADComputers | ForEach-Object {
6191 [string] $CurrentPassword = $_.'ms-mcs-admpwd'
6192 If ($_.'ms-mcs-admpwdexpirationtime' -ge 0)
6193 {
6194 $CurrentExpiration = [dateTime]::FromFileTime("$($_.'ms-mcs-admpwdexpirationtime')")
6195 }
6196 Else
6197 {
6198 $CurrentExpiration = "NA"
6199 }
6200 $PasswordAvailable = $false
6201 $PasswordStored = $true
6202 If ($CurrentPassword.length -ge 1)
6203 {
6204 $PasswordAvailable = $true
6205 }
6206 If ($CurrentExpiration -eq "NA")
6207 {
6208 $PasswordStored = $false
6209 $PasswordAvailable = "NA"
6210 $CurrentPassword = $null
6211 }
6212 If ($null -ne $_.dnshostname)
6213 {
6214 $CurrentHostname = $_.dnshostname
6215 }
6216 Else
6217 {
6218 $CurrentHostname = $_.cn
6219 }
6220 # Create the object for each instance.
6221 $Obj = New-Object PSObject
6222 $Obj | Add-Member -MemberType NoteProperty -Name Hostname -Value $CurrentHostname
6223 $Obj | Add-Member -MemberType NoteProperty -Name Stored -Value $PasswordStored
6224 $Obj | Add-Member -MemberType NoteProperty -Name Readable -Value $PasswordAvailable
6225 $Obj | Add-Member -MemberType NoteProperty -Name Password -Value $CurrentPassword
6226 $Obj | Add-Member -MemberType NoteProperty -Name Expiration -Value $CurrentExpiration
6227 $LAPSObj += $Obj
6228 Remove-Variable CurrentHostname
6229 Remove-Variable PasswordStored
6230 Remove-Variable PasswordAvailable
6231 Remove-Variable CurrentPassword
6232 Remove-Variable CurrentExpiration
6233 }
6234 Remove-Variable ADComputers
6235 }
6236 }
6237
6238 If ($Protocol -eq 'LDAP')
6239 {
6240 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
6241 $ObjSearcher.PageSize = $PageSize
6242 $ObjSearcher.Filter = "(objectClass=computer)"
6243 $ObjSearcher.PropertiesToLoad.AddRange(("cn","dnshostname","ms-mcs-admpwdexpirationtime","ms-mcs-admpwd"))
6244 $ObjSearcher.SearchScope = "Subtree"
6245 Try
6246 {
6247 $ADComputers = $ObjSearcher.FindAll()
6248 }
6249 Catch
6250 {
6251 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6252 Return $null
6253 }
6254 $ObjSearcher.dispose()
6255
6256 If ($($ADComputers | ForEach-Object {$_.Properties.'ms-mcs-admpwdexpirationtime'} | Measure-Object | Select-Object -ExpandProperty Count) -eq 0)
6257 {
6258 Write-Output "[*] LAPS is not implemented."
6259 }
6260 Else
6261 {
6262 $LAPSObj = @()
6263 $ADComputers | ForEach-Object {
6264 [string] $CurrentPassword = $_.properties.'ms-mcs-admpwd'
6265 If ($_.properties.'ms-mcs-admpwdexpirationtime' -ge 0)
6266 {
6267 $CurrentExpiration = [dateTime]::FromFileTime("$($_.properties.'ms-mcs-admpwdexpirationtime')")
6268 }
6269 Else
6270 {
6271 $CurrentExpiration = "NA"
6272 }
6273 $PasswordAvailable = $false
6274 $PasswordStored = $true
6275 If ($CurrentPassword.length -ge 1)
6276 {
6277 $PasswordAvailable = $true
6278 }
6279 If ($CurrentExpiration -eq "NA")
6280 {
6281 $PasswordStored = $false
6282 $PasswordAvailable = "NA"
6283 $CurrentPassword = $null
6284 }
6285 If ($null -ne $_.properties.dnshostname)
6286 {
6287 $CurrentHostname = ([string] $($_.properties.dnshostname))
6288 }
6289 Else
6290 {
6291 $CurrentHostname = ([string] $($_.properties.cn))
6292 }
6293 # Create the object for each instance.
6294 $Obj = New-Object PSObject
6295 $Obj | Add-Member -MemberType NoteProperty -Name Hostname -Value $CurrentHostname
6296 $Obj | Add-Member -MemberType NoteProperty -Name Stored -Value $PasswordStored
6297 $Obj | Add-Member -MemberType NoteProperty -Name Readable -Value $PasswordAvailable
6298 $Obj | Add-Member -MemberType NoteProperty -Name Password -Value $CurrentPassword
6299 $Obj | Add-Member -MemberType NoteProperty -Name Expiration -Value $CurrentExpiration
6300 $LAPSObj += $Obj
6301 Remove-Variable CurrentHostname
6302 Remove-Variable PasswordStored
6303 Remove-Variable PasswordAvailable
6304 Remove-Variable CurrentPassword
6305 Remove-Variable CurrentExpiration
6306 }
6307 Remove-Variable ADComputers
6308 }
6309 }
6310
6311 If ($LAPSObj)
6312 {
6313 Switch ($OutputType)
6314 {
6315 'STDOUT' { $LAPSObj }
6316 'CSV'
6317 {
6318 Write-Verbose "[+] LAPS"
6319 $ADFileName = -join($ReportPath,'\','LAPS','.csv')
6320 Try
6321 {
6322 $LAPSObj | Export-Csv -Path $ADFileName -NoTypeInformation
6323 }
6324 Catch
6325 {
6326 Write-Output "Failed to Export CSV File"
6327 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6328 }
6329 Remove-Variable LAPSObj
6330 Remove-Variable ADFileName
6331 }
6332 }
6333 }
6334}
6335
6336Function Get-ADRBitLocker
6337{
6338<#
6339.SYNOPSIS
6340 Returns all BitLocker Recovery Keys stored in the current (or specified) domain.
6341
6342.DESCRIPTION
6343 Returns all BitLocker Recovery Keys stored in the current (or specified) domain.
6344
6345.PARAMETER Protocol
6346 [string]
6347 Which protocol to use; ADWS (default) or LDAP.
6348
6349.PARAMETER UseAltCreds
6350 [bool]
6351 Whether to use provided credentials or not.
6352
6353.PARAMETER ReportPath
6354 [string]
6355 Path for ADRecon output folder.
6356
6357.PARAMETER objDomain
6358 [DirectoryServices.DirectoryEntry]
6359 Domain Directory Entry object.
6360
6361.PARAMETER OutputType
6362 [array]
6363 Output Type.
6364
6365.OUTPUTS
6366 CSV file is created in the folder specified with the information.
6367#>
6368 param(
6369 [Parameter(Mandatory = $true)]
6370 [string] $Protocol,
6371
6372 [Parameter(Mandatory = $true)]
6373 [bool] $UseAltCreds,
6374
6375 [Parameter(Mandatory = $true)]
6376 [string] $ReportPath,
6377
6378 [Parameter(Mandatory = $false)]
6379 [DirectoryServices.DirectoryEntry] $objDomain,
6380
6381 [Parameter(Mandatory = $true)]
6382 [array] $OutputType
6383 )
6384
6385 Write-Output "[-] BitLocker Recovery Keys - Needs Privileged Account"
6386 If ($Protocol -eq 'ADWS')
6387 {
6388 Try
6389 {
6390 $ADBitLockerRecoveryKeys = Get-ADObject -LDAPFilter '(objectClass=msFVE-RecoveryInformation)' -Properties distinguishedName,msFVE-RecoveryPassword
6391 }
6392 Catch
6393 {
6394 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6395 }
6396
6397 If ($ADBitLockerRecoveryKeys)
6398 {
6399 $cnt = $($ADBitLockerRecoveryKeys | Measure-Object | Select-Object -ExpandProperty Count)
6400 If ($cnt -ge 1)
6401 {
6402 Write-Output "[*] Total BitLocker Recovery Keys: $cnt"
6403 $BitLockerObj = @()
6404 $ADBitLockerRecoveryKeys | ForEach-Object {
6405 # Create the object for each instance.
6406 $Obj = New-Object PSObject
6407 $Obj | Add-Member -MemberType NoteProperty -Name "Distinguished Name" -Value $_.distinguishedName
6408 $Obj | Add-Member -MemberType NoteProperty -Name "Recovery Password" -Value $_.'msFVE-RecoveryPassword'
6409 $BitLockerObj += $Obj
6410 }
6411 }
6412 Remove-Variable ADBitLockerRecoveryKeys
6413 }
6414 }
6415
6416 If ($Protocol -eq 'LDAP')
6417 {
6418 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain
6419 $ObjSearcher.PageSize = $PageSize
6420 $ObjSearcher.Filter = "(objectClass=msFVE-RecoveryInformation)"
6421 $ObjSearcher.SearchScope = "Subtree"
6422
6423 Try
6424 {
6425 $ADBitLockerRecoveryKeys = $ObjSearcher.FindAll()
6426 }
6427 Catch
6428 {
6429 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6430 }
6431 $ObjSearcher.dispose()
6432
6433 If ($ADBitLockerRecoveryKeys)
6434 {
6435 $cnt = $($ADBitLockerRecoveryKeys | Measure-Object | Select-Object -ExpandProperty Count)
6436 If ($cnt -ge 1)
6437 {
6438 Write-Output "[*] Total BitLocker Recovery Keys: $cnt"
6439 $BitLockerObj = @()
6440 $ADBitLockerRecoveryKeys | ForEach-Object {
6441 # Create the object for each instance.
6442 $Obj = New-Object PSObject
6443 $Obj | Add-Member -MemberType NoteProperty -Name "Distinguished Name" -Value ([string]($_.Properties.distinguishedname))
6444 $Obj | Add-Member -MemberType NoteProperty -Name "Recovery Password" -Value $_.Properties.'msfve-recoverypassword'
6445 $BitLockerObj += $Obj
6446 }
6447 }
6448 Remove-Variable cnt
6449 Remove-Variable ADBitLockerRecoveryKeys
6450 }
6451 }
6452
6453 If ($BitLockerObj)
6454 {
6455 Switch ($OutputType)
6456 {
6457 'STDOUT' { $BitLockerObj }
6458 'CSV'
6459 {
6460 Write-Verbose "[+] BitLocker Recovery Keys"
6461 $ADFileName = -join($ReportPath,'\','BitLockerRecoveryKeys','.csv')
6462 Try
6463 {
6464 $BitLockerObj | Export-Csv -Path $ADFileName -NoTypeInformation
6465 }
6466 Catch
6467 {
6468 Write-Output "Failed to Export CSV File"
6469 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6470 }
6471 Remove-Variable BitLockerObj
6472 Remove-Variable ADFileName
6473 }
6474 }
6475 }
6476}
6477
6478Function Get-ADRGPOReport
6479{
6480<#
6481.SYNOPSIS
6482 Runs the Get-GPOReport cmdlet if available.
6483
6484.DESCRIPTION
6485 Runs the Get-GPOReport cmdlet if available and saves in HTML and XML formats.
6486
6487.PARAMETER Protocol
6488 [string]
6489 Which protocol to use; ADWS (default) or LDAP.
6490
6491.PARAMETER UseAltCreds
6492 [bool]
6493 Whether to use provided credentials or not.
6494
6495.PARAMETER ReportPath
6496 [string]
6497 Path for ADRecon output folder.
6498
6499.OUTPUTS
6500 CSV file is created in the folder specified with the information.
6501#>
6502 param(
6503 [Parameter(Mandatory = $true)]
6504 [string] $Protocol,
6505
6506 [Parameter(Mandatory = $true)]
6507 [bool] $UseAltCreds,
6508
6509 [Parameter(Mandatory = $true)]
6510 [string] $ReportPath
6511 )
6512
6513 Write-Output "[-] Domain GPO Report - May take some time"
6514 If ($Protocol -eq 'ADWS')
6515 {
6516 Try
6517 {
6518 Import-Module GroupPolicy -WarningAction Stop -ErrorAction Stop | Out-Null
6519 }
6520 Catch
6521 {
6522 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6523 Return $null
6524 }
6525 Try
6526 {
6527 Write-Output "[*] Domain GPO Report XML"
6528 $ADFileName = -join($ReportPath,'\','GPO-Report','.xml')
6529 Get-GPOReport -All -ReportType XML -Path $ADFileName
6530
6531 }
6532 Catch
6533 {
6534 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6535 If ($UseAltCreds)
6536 {
6537 Write-Output "[*] Run the tool using RUNAS."
6538 Write-Output "[*] runas /user:<Domain FQDN>\<Username> /netonly powershell.exe"
6539 }
6540 Return $null
6541 }
6542 Try
6543 {
6544 Write-Output "[*] Domain GPO Report HTML"
6545 $ADFileName = -join($ReportPath,'\','GPO-Report','.html')
6546 Get-GPOReport -All -ReportType HTML -Path $ADFileName
6547
6548 }
6549 Catch
6550 {
6551 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6552 If ($UseAltCreds)
6553 {
6554 Write-Output "[*] Run the tool using RUNAS."
6555 Write-Output "[*] runas /user:<Domain FQDN>\<Username> /netonly powershell.exe"
6556 }
6557 Return $null
6558 }
6559 }
6560 If ($Protocol -eq 'LDAP')
6561 {
6562 Write-Output "[*] Currently, the module is only supported with ADWS."
6563 }
6564}
6565
6566Function Remove-EmptyADROutputDir
6567{
6568<#
6569.SYNOPSIS
6570 Removes ADRecon output folder if empty.
6571
6572.DESCRIPTION
6573 Removes ADRecon output folder if empty.
6574
6575.PARAMETER OutputDir
6576 [string]
6577 Path for ADRecon output folder.
6578#>
6579 param(
6580 [Parameter(Mandatory = $true)]
6581 [string] $OutputDir
6582 )
6583 If (!(Test-Path -Path $OutputDir\*))
6584 {
6585 Remove-Item $OutputDir
6586 Write-Verbose "Removed Empty Directory $OutputDir"
6587 }
6588}
6589
6590Function Invoke-ADRecon
6591{
6592<#
6593.SYNOPSIS
6594 Wrapper function to run ADRecon modules.
6595
6596.DESCRIPTION
6597 Wrapper function to set variables, check dependencies and run ADRecon modules.
6598
6599.PARAMETER Protocol
6600 [string]
6601 Which protocol to use; ADWS (default) or LDAP.
6602
6603.PARAMETER Collect
6604 [array]
6605 What attributes to collect; Forest, Domain, PasswordPolicy, DCs, Users, UserSPNs, Groups, GroupMembers, OUs, OUPermissions, GPOs, GPOReport, DNSZones, Printers, Computers, ComputerSPNs, LAPS, BitLocker
6606
6607.PARAMETER DCIP
6608 [string]
6609 IP Address of the Domain Controller.
6610
6611.PARAMETER creds
6612 [Management.Automation.PSCredential]
6613 Credentials.
6614
6615.PARAMETER OutputDir
6616 [string]
6617 Path for ADRecon output folder to save the CSV files and the ADRecon-Report.xlsx.
6618
6619.PARAMETER DormantTimeSpan
6620 [int]
6621 Timespan for Dormant accounts. Default 90 days.
6622
6623.PARAMETER PageSize
6624 [int]
6625 The PageSize to set for the LDAP searcher object. Default 200.
6626
6627.PARAMETER Threads
6628 [int]
6629 The number of threads to use during processing of objects. Default 10.
6630
6631.PARAMETER FlushCount
6632 [int]
6633 The number of processed objects which will be flushed to disk. Default -1 (After all objects are processed).
6634
6635.PARAMETER UseAltCreds
6636 [bool]
6637 Whether to use provided credentials or not.
6638
6639.OUTPUTS
6640 CSV file is created in the folder specified with the information.
6641#>
6642 param(
6643 [Parameter(Mandatory = $false)]
6644 [string] $GenExcel,
6645
6646 [Parameter(Mandatory = $true)]
6647 [string] $Protocol,
6648
6649 [Parameter(Mandatory = $true)]
6650 [array] $Collect,
6651
6652 [Parameter(Mandatory = $false)]
6653 [string] $DCIP,
6654
6655 [Parameter(Mandatory = $false)]
6656 [Management.Automation.PSCredential] $creds = [Management.Automation.PSCredential]::Empty,
6657
6658 [Parameter(Mandatory = $true)]
6659 [array] $OutputType,
6660
6661 [Parameter(Mandatory = $false)]
6662 [string] $ExcelPath,
6663
6664 [Parameter(Mandatory = $true)]
6665 [int] $DormantTimeSpan = 90,
6666
6667 [Parameter(Mandatory = $true)]
6668 [int] $PageSize = 200,
6669
6670 [Parameter(Mandatory = $true)]
6671 [int] $Threads = 10,
6672
6673 [Parameter(Mandatory = $true)]
6674 [int] $FlushCount = -1,
6675
6676 [Parameter(Mandatory = $false)]
6677 [bool] $UseAltCreds = $false
6678 )
6679
6680 [string] $ADReconVersion = "v180208"
6681 Write-Output "[*] ADRecon $ADReconVersion by Prashant Mahajan (@prashant3535) from Sense of Security."
6682
6683 If ($GenExcel)
6684 {
6685 If (!(Test-Path $GenExcel))
6686 {
6687 Write-Output "[ERROR] Invalid Path ... Exiting"
6688 Return $null
6689 }
6690 Get-ADRGenExcel $GenExcel
6691 Return $null
6692 }
6693
6694 Try
6695 {
6696 If ($PSVersionTable.PSVersion.Major -ne 2)
6697 {
6698 $computer = Get-CimInstance -ClassName Win32_ComputerSystem
6699 $computerdomainrole = ($computer).DomainRole
6700 }
6701 Else
6702 {
6703 $computer = Get-WMIObject win32_computersystem
6704 $computerdomainrole = ($computer).DomainRole
6705 }
6706 }
6707 Catch
6708 {
6709 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6710 }
6711
6712 switch ($computerdomainrole)
6713 {
6714 0
6715 {
6716 [string] $computerrole = "Standalone Workstation"
6717 $Env:ADPS_LoadDefaultDrive = 0
6718 $UseAltCreds = $true
6719 }
6720 1 { [string] $computerrole = "Member Workstation" }
6721 2
6722 {
6723 [string] $computerrole = "Standalone Server"
6724 $UseAltCreds = $true
6725 $Env:ADPS_LoadDefaultDrive = 0
6726 }
6727 3 { [string] $computerrole = "Member Server" }
6728 4 { [string] $computerrole = "Backup Domain Controller" }
6729 5 { [string] $computerrole = "Primary Domain Controller" }
6730 default { Write-Output "Computer Role could not be identified." }
6731 }
6732
6733 If (($DCIP -ne "") -or ($creds -ne [Management.Automation.PSCredential]::Empty))
6734 {
6735 $UseAltCreds = $true
6736 }
6737
6738 If ($Protocol -eq 'ADWS')
6739 {
6740 Try
6741 {
6742 Import-Module ActiveDirectory -WarningAction Stop -ErrorAction Stop | Out-Null
6743 }
6744 Catch
6745 {
6746 Write-Warning "ActiveDirectory Module from RSAT (Remote Server Administration Tools) is not installed. ... Continuing with LDAP"
6747 $Protocol = 'LDAP'
6748 }
6749 }
6750
6751 Try
6752 {
6753 $CLR4 = ([System.Reflection.Assembly]::GetExecutingAssembly().ImageRuntimeVersion)[1]
6754 If ($Protocol -eq 'ADWS')
6755 {
6756 If ($CLR4 -eq "4")
6757 {
6758 Add-Type -TypeDefinition $ADWSSource -ReferencedAssemblies ([system.reflection.assembly]::LoadWithPartialName("Microsoft.ActiveDirectory.Management")).Location
6759 }
6760 Else
6761 {
6762 Add-Type -TypeDefinition $ADWSSource -ReferencedAssemblies ([system.reflection.assembly]::LoadWithPartialName("Microsoft.ActiveDirectory.Management")).Location -Language CSharpVersion3
6763 }
6764 }
6765
6766 If ($Protocol -eq 'LDAP')
6767 {
6768 If ($CLR4 -eq "4")
6769 {
6770 Add-Type -TypeDefinition $LDAPSource -ReferencedAssemblies ([system.reflection.assembly]::LoadWithPartialName("System.DirectoryServices")).Location
6771 }
6772 Else
6773 {
6774 Add-Type -TypeDefinition $LDAPSource -ReferencedAssemblies ([system.reflection.assembly]::LoadWithPartialName("System.DirectoryServices")).Location -Language CSharpVersion3
6775 }
6776 # Allow running using RUNAS from a non-domain joined machine
6777 # runas /user:<Domain FQDN>\<Username> /netonly powershell.exe
6778 If (($DCIP -eq "") -and ($creds -eq [Management.Automation.PSCredential]::Empty))
6779 {
6780 Try
6781 {
6782 $objDomain = [ADSI]""
6783 $UseAltCreds = $false
6784 $objDomain.Dispose()
6785 }
6786 Catch
6787 {
6788 $UseAltCreds = $true
6789 }
6790 }
6791 }
6792 }
6793 Catch
6794 {
6795 Write-Output "[ERROR] $($_.Exception.Message)"
6796 Return $null
6797 }
6798
6799 If ($UseAltCreds -and (($DCIP -eq "") -or ($creds -eq [Management.Automation.PSCredential]::Empty)))
6800 {
6801 If (($DCIP -ne "") -and ($creds -eq [Management.Automation.PSCredential]::Empty))
6802 {
6803 Try
6804 {
6805 $creds = Get-Credential
6806 }
6807 Catch
6808 {
6809 Write-Output "[ERROR] $($_.Exception.Message)"
6810 Return $null
6811 }
6812 }
6813 Else
6814 {
6815 Write-Output "Run Get-Help .\ADRecon.ps1 -Examples for additional information."
6816 Write-Output "[ERROR] Use the -DomainController and -Credential parameter."`n
6817 Return $null
6818 }
6819 }
6820
6821 Write-Output $computerrole
6822 Write-Output ($computer).domain
6823
6824 Remove-Variable computer
6825 Remove-Variable computerdomainrole
6826
6827 Switch ($Collect)
6828 {
6829 'Forest' { $ADRForest = $true }
6830 'Domain' {$ADRDomain = $true }
6831 'PasswordPolicy' { $ADRPasswordPolicy = $true }
6832 'DCs' { $ADRDCs = $true }
6833 'Users' { $ADRUsers = $true }
6834 'UserSPNs' { $ADRUserSPNs = $true }
6835 'Groups' { $ADRGroups = $true }
6836 'GroupMembers' { $ADRGroupMembers = $true }
6837 'OUs' { $ADROUs = $true }
6838 'OUPermissions' { $ADROUPermissions = $true }
6839 'GPOs' { $ADRGPOs = $true }
6840 'GPOReport' { $ADRGPOReport = $true }
6841 'DNSZones' { $ADRDNSZones = $true }
6842 'Printers' { $ADRPrinters = $true }
6843 'Computers' { $ADRComputers = $true }
6844 'ComputerSPNs' { $ADRCopmuterSPNs = $true }
6845 'BitLocker' { $ADRBitLocker = $true }
6846 'LAPS' { $ADRLAPS = $true }
6847 'Default'
6848 {
6849 $ADRForest = $true
6850 $ADRDomain = $true
6851 $ADRPasswordPolicy = $true
6852 $ADRDCs = $true
6853 $ADRUsers = $true
6854 $ADRUserSPNs = $true
6855 $ADRGroups = $true
6856 $ADRGroupMembers = $true
6857 $ADROUs = $true
6858 $ADROUPermissions = $true
6859 $ADRGPOs = $true
6860 $ADRGPOReport = $false
6861 $ADRDNSZones = $true
6862 $ADRPrinters = $true
6863 $ADRComputers = $true
6864 $ADRCopmuterSPNs = $true
6865 $ADRLAPS = $true
6866 $ADRBitLocker = $true
6867 If ($OutputType = "Default")
6868 {
6869 [array] $OutputType = "CSV","Excel"
6870 }
6871 }
6872 }
6873
6874 Switch ($OutputType)
6875 {
6876 'CSV' { $ADRCSV = $true }
6877 'STDOUT' { $ADRSTDOUT = $true }
6878 'Excel' { $ADRExcel = $true }
6879 'Default' { [array] $OutputType = "STDOUT" }
6880 }
6881
6882 If ($ADRExcel)
6883 {
6884 If (!($ADRCSV))
6885 {
6886 $ADRCSV = $true
6887 If ($ADRSTDOUT)
6888 {
6889 [array] $OutputType = "CSV","STDOUT","Excel"
6890 }
6891 Else
6892 {
6893 [array] $OutputType = "CSV","Excel"
6894 }
6895 }
6896 }
6897
6898 $returndir = Get-Location
6899 $date = Get-Date
6900
6901 If ($ExcelPath)
6902 {
6903 If (!(Test-Path $ExcelPath))
6904 {
6905 New-Item $ExcelPath -type directory | Out-Null
6906 If (!(Test-Path $ExcelPath))
6907 {
6908 Write-Output "[ERROR] Invalid OutputDir Path ... Exiting"
6909 Return $null
6910 }
6911 }
6912 $ExcelPath = $((Convert-Path $ExcelPath).TrimEnd("\"))
6913 Write-Verbose $ExcelPath
6914 }
6915
6916 If ($ADRCSV)
6917 {
6918 If (-Not $ExcelPath)
6919 {
6920 $ExcelPath = -join($returndir,'\','ADRecon-Report-',$date.day,$date.Month,$date.Year,$date.Hour,$date.Minute,$date.Second)
6921 New-Item $ExcelPath -type directory | Out-Null
6922 }
6923 $ReportPath = [System.IO.DirectoryInfo] -join($ExcelPath,'\','CSV-Files')
6924 New-Item $ReportPath -type directory | Out-Null
6925
6926 If (!(Test-Path $ReportPath))
6927 {
6928 Write-Output "[ERROR] Could not create output directory"
6929 return $null
6930 }
6931 }
6932 Else
6933 {
6934 If ($ADRGPOReport)
6935 {
6936 If (-Not $ExcelPath)
6937 {
6938 $ExcelPath = -join($returndir,'\','ADRecon-Report-',$date.day,$date.Month,$date.Year,$date.Hour,$date.Minute,$date.Second)
6939 New-Item $ExcelPath -type directory | Out-Null
6940 }
6941 $ReportPath = $ExcelPath
6942 }
6943 Else
6944 {
6945 $ReportPath = $returndir
6946 }
6947 }
6948
6949 If ($UseAltCreds -and ($Protocol -eq 'ADWS'))
6950 {
6951 If (!(Test-Path ADR:))
6952 {
6953 Try
6954 {
6955 New-PSDrive -PSProvider ActiveDirectory -Name ADR -Root "" -Server $DCIP -Credential $creds -ErrorAction Stop | Out-Null
6956 }
6957 Catch
6958 {
6959 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6960 If ($ReportPath)
6961 {
6962 Remove-EmptyADROutputDir $ReportPath
6963 }
6964 If ($ExcelPath)
6965 {
6966 Remove-EmptyADROutputDir $ExcelPath
6967 }
6968 Return $null
6969 }
6970 }
6971 Else
6972 {
6973 Remove-PSDrive ADR
6974 Try
6975 {
6976 New-PSDrive -PSProvider ActiveDirectory -Name ADR -Root "" -Server $DCIP -Credential $creds -ErrorAction Stop | Out-Null
6977 }
6978 Catch
6979 {
6980 Write-Output "[EXCEPTION] $($_.Exception.Message)"
6981 If ($ReportPath)
6982 {
6983 Remove-EmptyADROutputDir $ReportPath
6984 }
6985 If ($ExcelPath)
6986 {
6987 Remove-EmptyADROutputDir $ExcelPath
6988 }
6989 Return $null
6990 }
6991 }
6992 Set-Location ADR:
6993 }
6994
6995 If ($Protocol -eq 'LDAP')
6996 {
6997 If ($UseAltCreds)
6998 {
6999 Try
7000 {
7001 $objDomain = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)", $creds.UserName,$creds.GetNetworkCredential().Password
7002 $objDomainRootDSE = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DCIP)/RootDSE", $creds.UserName,$creds.GetNetworkCredential().Password
7003 }
7004 Catch
7005 {
7006 Write-Output "[ERROR] $($_.Exception.Message)"
7007 If ($ReportPath)
7008 {
7009 Remove-EmptyADROutputDir $ReportPath
7010 }
7011 If ($ExcelPath)
7012 {
7013 Remove-EmptyADROutputDir $ExcelPath
7014 }
7015 Return $null
7016 }
7017 If(!($objDomain.name))
7018 {
7019 Write-Output "[ERROR] LDAP bind Unsuccessful"
7020 If ($ReportPath)
7021 {
7022 Remove-EmptyADROutputDir $ReportPath
7023 }
7024 If ($ExcelPath)
7025 {
7026 Remove-EmptyADROutputDir $ExcelPath
7027 }
7028 Return $null
7029 }
7030 Else
7031 {
7032 Write-Output "[*] LDAP bind Successful"
7033 }
7034 }
7035 Else
7036 {
7037 $objDomain = [ADSI]""
7038 $objDomainRootDSE = ([ADSI] "LDAP://RootDSE")
7039 If(!($objDomain.name))
7040 {
7041 Write-Output "[ERROR] LDAP bind Unsuccessful"
7042 If ($ReportPath)
7043 {
7044 Remove-EmptyADROutputDir $ReportPath
7045 }
7046 If ($ExcelPath)
7047 {
7048 Remove-EmptyADROutputDir $ExcelPath
7049 }
7050 Return $null
7051 }
7052 }
7053 }
7054
7055 Write-Output "[*] Commencing - $date"
7056 If ($ADRDomain) { Get-ADRDomain $Protocol $UseAltCreds $ReportPath $objDomain $objDomainRootDSE $DCIP $creds $OutputType }
7057 If ($ADRForest) { Get-ADRForest $Protocol $UseAltCreds $ReportPath $objDomain $objDomainRootDSE $DCIP $creds $OutputType }
7058 If ($ADRPasswordPolicy) { Get-ADRPassPol $Protocol $UseAltCreds $ReportPath $objDomain $OutputType }
7059 If ($ADRDCs) { Get-ADRDC $Protocol $UseAltCreds $ReportPath $objDomain $OutputType }
7060 If ($ADRUsers) { Get-ADRUser $Protocol $UseAltCreds $ReportPath $date $objDomain $DormantTimeSpan $PageSize $Threads $FlushCount $OutputType }
7061 If ($ADRUserSPNs) { Get-ADRUserSPN $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $Threads $FlushCount $OutputType }
7062 If ($ADRGroups) { Get-ADRGroup $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $Threads $FlushCount $OutputType }
7063 If ($ADRGroupMembers) { Get-ADRGroupMember $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $Threads $FlushCount $OutputType }
7064 If ($ADROUs) { Get-ADROU $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $OutputType }
7065 If ($ADROUPermissions) { Get-ADROUPermission $Protocol $UseAltCreds $ReportPath $objDomain $DCIP $creds $PageSize $OutputType }
7066 If ($ADRGPOs) { Get-ADRGPO $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $OutputType }
7067 If ($ADRDNSZones) { Get-ADRDNSZone $Protocol $UseAltCreds $ReportPath $objDomain $DCIP $creds $PageSize $OutputType }
7068 If ($ADRPrinters) { Get-ADRPrinter $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $OutputType }
7069 If ($ADRComputers) { Get-ADRComputer $Protocol $UseAltCreds $ReportPath $date $objDomain $PageSize $Threads $FlushCount $OutputType }
7070 If ($ADRCopmuterSPNs) { Get-ADRComputerSPN $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $Threads $FlushCount $OutputType }
7071 If ($ADRLAPS) { Get-ADRLAPSCheck $Protocol $UseAltCreds $ReportPath $objDomain $PageSize $OutputType }
7072 If ($ADRBitLocker) { Get-ADRBitLocker $Protocol $UseAltCreds $ReportPath $objDomain $OutputType }
7073 If ($ADRGPOReport) { Get-ADRGPOReport $Protocol $UseAltCreds $ReportPath }
7074 Switch ($OutputType)
7075 {
7076 'CSV'
7077 {
7078 $AboutADRecon = New-Object PSObject
7079 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "Category" -Value "Value"
7080 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "Date" -Value $($date)
7081 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "ADRecon" -Value "https://github.com/sense-of-security/ADRecon"
7082 If ($Protocol -eq 'ADWS')
7083 {
7084 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "RSAT Version" -Value $($ADReconVersion)
7085 }
7086 Else
7087 {
7088 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "LDAP Version" -Value $($ADReconVersion)
7089 }
7090 If ($UseAltCreds)
7091 {
7092 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "Ran as user" -Value $($creds.UserName)
7093 }
7094 Else
7095 {
7096 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "Ran as user" -Value $([Environment]::UserName)
7097 }
7098 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "Ran from" -Value $([Environment]::MachineName)
7099 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "Computer Role" -Value $($computerrole)
7100 $TotalTime = "{0:N2}" -f ((Get-DateDiff (Get-Date) $date).TotalMinutes)
7101 $AboutADRecon | Add-Member -MemberType NoteProperty -Name "Execution Time (mins)" -Value $($TotalTime)
7102
7103 Write-Verbose "[+] AboutADRecon"
7104 If ($AboutADRecon)
7105 {
7106 $ADFileName = -join($ReportPath,'\','AboutADRecon','.csv')
7107 Try
7108 {
7109 $AboutADRecon | Export-Csv -Path $ADFileName -NoTypeInformation
7110 }
7111 Catch
7112 {
7113 Write-Output "[ERROR] Failed to Export CSV File"
7114 Write-Output "[EXCEPTION] $($_.Exception.Message)"
7115 }
7116 Remove-Variable AboutADRecon
7117 Remove-Variable ADFileName
7118 }
7119 Write-Output "[*] Total Execution Time (mins): $($TotalTime)"
7120 Remove-Variable TotalTime
7121 Write-Output "[*] Completed."
7122 Write-Output "[*] Output Directory: $ExcelPath"
7123 }
7124 'STDOUT'
7125 {
7126 $TotalTime = "{0:N2}" -f ((Get-DateDiff (Get-Date) $date).TotalMinutes)
7127 Write-Output "[*] Total Execution Time (mins): $($TotalTime)"
7128 Remove-Variable TotalTime
7129 }
7130 'EXCEL'
7131 {
7132 Get-ADRGenExcel($ExcelPath)
7133 }
7134 }
7135 Set-Location $returndir
7136 Remove-Variable returndir
7137
7138 If (($Protocol -eq 'ADWS') -and $UseAltCreds)
7139 {
7140 Remove-PSDrive ADR
7141 }
7142
7143 If ($Protocol -eq 'LDAP')
7144 {
7145 $objDomain.Dispose()
7146 $objDomainRootDSE.Dispose()
7147 }
7148
7149 If ($ReportPath)
7150 {
7151 Remove-EmptyADROutputDir $ReportPath
7152 }
7153 If ($ExcelPath)
7154 {
7155 Remove-EmptyADROutputDir $ExcelPath
7156 }
7157
7158 Remove-Variable ADReconVersion
7159 Remove-Variable computerrole
7160}
7161
7162Invoke-ADRecon $GenExcel $Protocol $Collect $DomainController $Credential $OutputType $OutputDir $DormantTimeSpan $PageSize $Threads $FlushCount